View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.filters;
21  
22  import static com.puppycrawl.tools.checkstyle.checks.whitespace.FileTabCharacterCheck.MSG_CONTAINS_TAB;
23  import static com.puppycrawl.tools.checkstyle.checks.whitespace.FileTabCharacterCheck.MSG_FILE_CONTAINS_TAB;
24  import static org.junit.Assert.assertEquals;
25  import static org.junit.Assert.assertTrue;
26  import static org.junit.Assert.fail;
27  
28  import java.io.FileNotFoundException;
29  import java.io.IOException;
30  import java.util.Arrays;
31  import java.util.Collection;
32  import java.util.stream.Collectors;
33  
34  import org.junit.Assert;
35  import org.junit.Test;
36  
37  import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
38  import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
39  import com.puppycrawl.tools.checkstyle.api.AuditEvent;
40  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
41  import com.puppycrawl.tools.checkstyle.api.Configuration;
42  import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
43  import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
44  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
45  import com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineCheck;
46  import com.puppycrawl.tools.checkstyle.checks.whitespace.FileTabCharacterCheck;
47  import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
48  import nl.jqno.equalsverifier.EqualsVerifier;
49  
50  public class SuppressWithPlainTextCommentFilterTest extends AbstractModuleTestSupport {
51  
52      private static final String MSG_REGEXP_EXCEEDED = "regexp.exceeded";
53  
54      @Override
55      protected String getPackageLocation() {
56          return "com/puppycrawl/tools/checkstyle/filters/suppresswithplaintextcommentfilter";
57      }
58  
59      @Test
60      public void testFilterWithDefaultConfig() throws Exception {
61          final DefaultConfiguration filterCfg =
62              createModuleConfig(SuppressWithPlainTextCommentFilter.class);
63  
64          final DefaultConfiguration checkCfg = createModuleConfig(FileTabCharacterCheck.class);
65          checkCfg.addAttribute("eachLine", "true");
66  
67          final String[] suppressed = {
68              "5:7: " + getCheckMessage(FileTabCharacterCheck.class, MSG_FILE_CONTAINS_TAB),
69              "10:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
70          };
71  
72          final String[] violationMessages = {
73              "5:7: " + getCheckMessage(FileTabCharacterCheck.class, MSG_FILE_CONTAINS_TAB),
74              "8:7: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
75              "10:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
76          };
77  
78          verifySuppressed(
79              "InputSuppressWithPlainTextCommentFilterWithDefaultCfg.java",
80              removeSuppressed(violationMessages, suppressed),
81              filterCfg, checkCfg
82          );
83      }
84  
85      @Test
86      public void testChangeOffAndOnFormat() throws Exception {
87          final DefaultConfiguration filterCfg =
88              createModuleConfig(SuppressWithPlainTextCommentFilter.class);
89          filterCfg.addAttribute("onCommentFormat", "cs-on");
90          filterCfg.addAttribute("offCommentFormat", "cs-off");
91  
92          final DefaultConfiguration checkCfg = createModuleConfig(FileTabCharacterCheck.class);
93          checkCfg.addAttribute("eachLine", "true");
94  
95          final String[] suppressed = {
96              "5:7: " + getCheckMessage(FileTabCharacterCheck.class, MSG_FILE_CONTAINS_TAB),
97              "10:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
98              "11:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
99              "13:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
100         };
101 
102         final String[] violationMessage = {
103             "5:7: " + getCheckMessage(FileTabCharacterCheck.class, MSG_FILE_CONTAINS_TAB),
104             "8:7: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
105             "10:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
106             "11:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
107         };
108 
109         verifySuppressed(
110             "InputSuppressWithPlainTextCommentFilterWithCustomOnAndOffComments.java",
111             removeSuppressed(violationMessage, suppressed),
112             filterCfg, checkCfg
113         );
114     }
115 
116     @Test
117     public void testSuppressionCommentsInXmlFile() throws Exception {
118         final DefaultConfiguration filterCfg =
119             createModuleConfig(SuppressWithPlainTextCommentFilter.class);
120         filterCfg.addAttribute("offCommentFormat", "CS-OFF");
121         filterCfg.addAttribute("onCommentFormat", "CS-ON");
122 
123         final DefaultConfiguration checkCfg = createModuleConfig(FileTabCharacterCheck.class);
124         checkCfg.addAttribute("eachLine", "true");
125 
126         final String[] suppressed = {
127             "7:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
128         };
129 
130         final String[] violationMessages = {
131             "7:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
132             "10:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
133         };
134 
135         verifySuppressed(
136             "InputSuppressWithPlainTextCommentFilter.xml",
137             removeSuppressed(violationMessages, suppressed),
138             filterCfg, checkCfg
139         );
140     }
141 
142     @Test
143     public void testSuppressionCommentsInPropertiesFile() throws Exception {
144         final DefaultConfiguration filterCfg =
145             createModuleConfig(SuppressWithPlainTextCommentFilter.class);
146         filterCfg.addAttribute("offCommentFormat", "# CHECKSTYLE:OFF");
147         filterCfg.addAttribute("onCommentFormat", "# CHECKSTYLE:ON");
148 
149         final DefaultConfiguration checkCfg = createModuleConfig(RegexpSinglelineCheck.class);
150         checkCfg.addAttribute("format", "^key[0-9]=$");
151 
152         final String[] suppressed = {
153             "2: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
154                 "^key[0-9]=$"),
155         };
156 
157         final String[] violationMessages = {
158             "2: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
159                 "^key[0-9]=$"),
160             "4: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
161                 "^key[0-9]=$"),
162         };
163 
164         verifySuppressed(
165             "InputSuppressWithPlainTextCommentFilter.properties",
166             removeSuppressed(violationMessages, suppressed),
167             filterCfg, checkCfg
168         );
169     }
170 
171     @Test
172     public void testSuppressionCommentsInSqlFile() throws Exception {
173         final DefaultConfiguration filterCfg =
174             createModuleConfig(SuppressWithPlainTextCommentFilter.class);
175         filterCfg.addAttribute("offCommentFormat", "-- CHECKSTYLE OFF");
176         filterCfg.addAttribute("onCommentFormat", "-- CHECKSTYLE ON");
177 
178         final DefaultConfiguration checkCfg = createModuleConfig(FileTabCharacterCheck.class);
179         checkCfg.addAttribute("eachLine", "true");
180 
181         final String[] suppressed = {
182             "2:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
183         };
184 
185         final String[] violationMessages = {
186             "2:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
187             "5:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
188         };
189 
190         verifySuppressed(
191             "InputSuppressWithPlainTextCommentFilter.sql",
192             removeSuppressed(violationMessages, suppressed),
193             filterCfg, checkCfg
194         );
195     }
196 
197     @Test
198     public void testSuppressionCommentsInJavaScriptFile() throws Exception {
199         final DefaultConfiguration filterCfg =
200             createModuleConfig(SuppressWithPlainTextCommentFilter.class);
201         filterCfg.addAttribute("offCommentFormat", "// CS-OFF");
202         filterCfg.addAttribute("onCommentFormat", "// CS-ON");
203 
204         final DefaultConfiguration checkCfg = createModuleConfig(RegexpSinglelineCheck.class);
205         checkCfg.addAttribute("format", ".*===.*");
206 
207         final String[] suppressed = {
208             "2: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED, ".*===.*"),
209         };
210 
211         final String[] violationMessages = {
212             "2: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED, ".*===.*"),
213             "5: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED, ".*===.*"),
214         };
215 
216         verifySuppressed(
217             "InputSuppressWithPlainTextCommentFilter.js",
218             removeSuppressed(violationMessages, suppressed),
219             filterCfg, checkCfg
220         );
221     }
222 
223     @Test
224     public void testInvalidCheckFormat() throws Exception {
225         final DefaultConfiguration filterCfg =
226             createModuleConfig(SuppressWithPlainTextCommentFilter.class);
227         filterCfg.addAttribute("checkFormat", "e[l");
228         filterCfg.addAttribute("onCommentFormat", "// cs-on");
229         filterCfg.addAttribute("offCommentFormat", "// cs-off");
230 
231         final DefaultConfiguration checkCfg = createModuleConfig(FileTabCharacterCheck.class);
232         checkCfg.addAttribute("eachLine", "true");
233 
234         final String[] suppressed = CommonUtils.EMPTY_STRING_ARRAY;
235 
236         final String[] violationMessages = {
237             "5:7: " + getCheckMessage(FileTabCharacterCheck.class, MSG_FILE_CONTAINS_TAB),
238             "8:7: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
239             "10:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
240         };
241 
242         try {
243             verifySuppressed(
244                 "InputSuppressWithPlainTextCommentFilterWithCustomOnAndOffComments.java",
245                 removeSuppressed(violationMessages, suppressed),
246                 filterCfg, checkCfg
247             );
248             fail("CheckstyleException is expected");
249         }
250         catch (CheckstyleException ex) {
251             final IllegalArgumentException cause = (IllegalArgumentException) ex.getCause();
252             assertEquals("Invalid exception message",
253                 "unable to parse expanded comment e[l", cause.getMessage());
254         }
255     }
256 
257     @Test
258     public void testInvalidMessageFormat() throws Exception {
259         final DefaultConfiguration filterCfg =
260             createModuleConfig(SuppressWithPlainTextCommentFilter.class);
261         filterCfg.addAttribute("messageFormat", "e[l");
262         filterCfg.addAttribute("onCommentFormat", "// cs-on");
263         filterCfg.addAttribute("offCommentFormat", "// cs-off");
264 
265         final DefaultConfiguration checkCfg = createModuleConfig(FileTabCharacterCheck.class);
266         checkCfg.addAttribute("eachLine", "true");
267 
268         final String[] suppressed = CommonUtils.EMPTY_STRING_ARRAY;
269 
270         final String[] violationMessages = {
271             "5:7: " + getCheckMessage(FileTabCharacterCheck.class, MSG_FILE_CONTAINS_TAB),
272             "8:7: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
273             "10:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
274         };
275 
276         try {
277             verifySuppressed(
278                 "InputSuppressWithPlainTextCommentFilterWithCustomOnAndOffComments.java",
279                 removeSuppressed(violationMessages, suppressed),
280                 filterCfg, checkCfg
281             );
282             fail("CheckstyleException is expected");
283         }
284         catch (CheckstyleException ex) {
285             final IllegalArgumentException cause = (IllegalArgumentException) ex.getCause();
286             assertEquals("Invalid exception message",
287                 "unable to parse expanded comment e[l", cause.getMessage());
288         }
289     }
290 
291     @Test
292     public void testAcceptNullLocalizedMessage() {
293         final SuppressWithPlainTextCommentFilter filter = new SuppressWithPlainTextCommentFilter();
294         final AuditEvent auditEvent = new AuditEvent(this);
295         assertTrue("Filter should accept audit event", filter.accept(auditEvent));
296         Assert.assertNull("File name should not be null", auditEvent.getFileName());
297     }
298 
299     @Test
300     public void testEqualsAndHashCodeOfTagClass() {
301         EqualsVerifier.forClass(SuppressWithPlainTextCommentFilter.Suppression.class)
302             .usingGetClass().verify();
303     }
304 
305     @Test
306     public void testSuppressByModuleId() throws Exception {
307         final DefaultConfiguration filterCfg =
308             createModuleConfig(SuppressWithPlainTextCommentFilter.class);
309         filterCfg.addAttribute("offCommentFormat", "CSOFF (\\w+) \\(\\w+\\)");
310         filterCfg.addAttribute("onCommentFormat", "CSON (\\w+)");
311         filterCfg.addAttribute("checkFormat", "$1");
312 
313         final DefaultConfiguration regexpCheckCfg = createModuleConfig(RegexpSinglelineCheck.class);
314         regexpCheckCfg.addAttribute("id", "ignore");
315         regexpCheckCfg.addAttribute("format", ".*[a-zA-Z][0-9].*");
316 
317         final DefaultConfiguration fileTabCheckCfg =
318             createModuleConfig(FileTabCharacterCheck.class);
319         fileTabCheckCfg.addAttribute("eachLine", "true");
320         fileTabCheckCfg.addAttribute("id", "foo");
321 
322         final String[] suppressedViolationMessages = {
323             "6: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
324                 ".*[a-zA-Z][0-9].*"),
325             "9: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
326                 ".*[a-zA-Z][0-9].*"),
327             "11: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
328                 ".*[a-zA-Z][0-9].*"),
329         };
330 
331         final String[] expectedViolationMessages = {
332             "6: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
333                 ".*[a-zA-Z][0-9].*"),
334             "9:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
335             "9: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
336                 ".*[a-zA-Z][0-9].*"),
337             "11: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
338                 ".*[a-zA-Z][0-9].*"),
339             "14: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
340                 ".*[a-zA-Z][0-9].*"),
341         };
342 
343         verifySuppressed(
344             "InputSuppressWithPlainTextCommentFilterSuppressById.java",
345             removeSuppressed(expectedViolationMessages, suppressedViolationMessages),
346             filterCfg, regexpCheckCfg, fileTabCheckCfg
347         );
348     }
349 
350     @Test
351     public void testSuppressByModuleIdWithNullModuleId() throws Exception {
352         final DefaultConfiguration filterCfg =
353             createModuleConfig(SuppressWithPlainTextCommentFilter.class);
354         filterCfg.addAttribute("offCommentFormat", "CSOFF (\\w+) \\(\\w+\\)");
355         filterCfg.addAttribute("onCommentFormat", "CSON (\\w+)");
356         filterCfg.addAttribute("checkFormat", "$1");
357 
358         final DefaultConfiguration regexpCheckCfg = createModuleConfig(RegexpSinglelineCheck.class);
359         regexpCheckCfg.addAttribute("id", "ignore");
360         regexpCheckCfg.addAttribute("format", ".*[a-zA-Z][0-9].*");
361 
362         final DefaultConfiguration fileTabCheckCfg =
363             createModuleConfig(FileTabCharacterCheck.class);
364         fileTabCheckCfg.addAttribute("eachLine", "true");
365         fileTabCheckCfg.addAttribute("id", null);
366 
367         final String[] suppressedViolationMessages = {
368             "6: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
369                 ".*[a-zA-Z][0-9].*"),
370             "9: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
371                 ".*[a-zA-Z][0-9].*"),
372             "11: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
373                 ".*[a-zA-Z][0-9].*"),
374             };
375 
376         final String[] expectedViolationMessages = {
377             "6: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
378                 ".*[a-zA-Z][0-9].*"),
379             "9:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
380             "9: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
381                 ".*[a-zA-Z][0-9].*"),
382             "11: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
383                 ".*[a-zA-Z][0-9].*"),
384             "14: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
385                 ".*[a-zA-Z][0-9].*"),
386         };
387 
388         verifySuppressed(
389             "InputSuppressWithPlainTextCommentFilterSuppressById.java",
390             removeSuppressed(expectedViolationMessages, suppressedViolationMessages),
391             filterCfg, regexpCheckCfg, fileTabCheckCfg
392         );
393     }
394 
395     @Test
396     public void testAcceptThrowsIllegalStateExceptionAsFileNotFound() {
397         final LocalizedMessage message = new LocalizedMessage(1, 1, 1, TokenTypes.CLASS_DEF,
398             "messages.properties", "key", null, SeverityLevel.ERROR, null, getClass(), null);
399         final String fileName = "nonexisting_file";
400         final AuditEvent auditEvent = new AuditEvent(this, fileName, message);
401 
402         final SuppressWithPlainTextCommentFilter filter = new SuppressWithPlainTextCommentFilter();
403 
404         try {
405             filter.accept(auditEvent);
406             fail(IllegalStateException.class.getSimpleName() + " is expected");
407         }
408         catch (IllegalStateException ex) {
409             assertEquals("Invalid exception message",
410                 "Cannot read source file: " + fileName, ex.getMessage());
411 
412             final Throwable cause = ex.getCause();
413             assertTrue("Exception cause has invalid type",
414                 cause instanceof FileNotFoundException);
415             assertEquals("Invalid exception message",
416                 fileName + " (No such file or directory)", cause.getMessage());
417         }
418     }
419 
420     @Test
421     public void testFilterWithCustomMessageFormat() throws Exception {
422         final DefaultConfiguration filterCfg =
423             createModuleConfig(SuppressWithPlainTextCommentFilter.class);
424         final String messageFormat =
425             ".*" + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB) + ".*";
426         // -@cs[CheckstyleTestMakeup] need to test dynamic property
427         filterCfg.addAttribute("messageFormat", messageFormat);
428 
429         final DefaultConfiguration fileTabCheckCfg =
430             createModuleConfig(FileTabCharacterCheck.class);
431         fileTabCheckCfg.addAttribute("eachLine", "true");
432 
433         final DefaultConfiguration regexpCheckCfg = createModuleConfig(RegexpSinglelineCheck.class);
434         regexpCheckCfg.addAttribute("id", "ignore");
435         regexpCheckCfg.addAttribute("format", ".*[a-zA-Z][0-9].*");
436 
437         final String[] suppressed = {
438             "8:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
439         };
440 
441         final String[] violationMessages = {
442             "6: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
443                 ".*[a-zA-Z][0-9].*"),
444             "8:1: " + getCheckMessage(FileTabCharacterCheck.class, MSG_CONTAINS_TAB),
445             "8: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
446                 ".*[a-zA-Z][0-9].*"),
447             "10: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
448                 ".*[a-zA-Z][0-9].*"),
449             "13: " + getCheckMessage(RegexpSinglelineCheck.class, MSG_REGEXP_EXCEEDED,
450                 ".*[a-zA-Z][0-9].*"),
451         };
452 
453         verifySuppressed(
454             "InputSuppressWithPlainTextCommentFilterCustomMessageFormat.java",
455             removeSuppressed(violationMessages, suppressed),
456             filterCfg, fileTabCheckCfg, regexpCheckCfg
457         );
458     }
459 
460     @Test
461     public void testFilterWithDirectory() throws IOException {
462         final SuppressWithPlainTextCommentFilter filter = new SuppressWithPlainTextCommentFilter();
463         final AuditEvent event = new AuditEvent(this, getPath(""), new LocalizedMessage(1, 1,
464                 "bundle", "key", null, SeverityLevel.ERROR, "moduleId", getClass(),
465                 "customMessage"));
466 
467         assertTrue("filter should accept directory", filter.accept(event));
468     }
469 
470     private void verifySuppressed(String fileNameWithExtension, String[] violationMessages,
471                                   Configuration... childConfigs) throws Exception {
472         final DefaultConfiguration checkerConfig = createRootConfig(null);
473 
474         Arrays.stream(childConfigs).forEach(checkerConfig::addChild);
475 
476         final String fileExtension = CommonUtils.getFileExtension(fileNameWithExtension);
477         checkerConfig.addAttribute("fileExtensions", fileExtension);
478 
479         verify(checkerConfig, getPath(fileNameWithExtension), violationMessages);
480     }
481 
482     private static String[] removeSuppressed(String[] from, String... remove) {
483         final Collection<String> coll = Arrays.stream(from).collect(Collectors.toList());
484         coll.removeAll(Arrays.asList(remove));
485         return coll.toArray(new String[coll.size()]);
486     }
487 }