1
2
3
4
5 package com.qulice.checkstyle;
6
7 import com.puppycrawl.tools.checkstyle.Checker;
8 import com.puppycrawl.tools.checkstyle.ConfigurationLoader;
9 import com.puppycrawl.tools.checkstyle.PropertiesExpander;
10 import com.puppycrawl.tools.checkstyle.api.AuditEvent;
11 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
12 import com.puppycrawl.tools.checkstyle.api.Configuration;
13 import com.qulice.spi.Environment;
14 import com.qulice.spi.ResourceValidator;
15 import com.qulice.spi.Violation;
16 import java.io.File;
17 import java.util.Collection;
18 import java.util.LinkedList;
19 import java.util.List;
20 import java.util.Properties;
21 import org.xml.sax.InputSource;
22
23
24
25
26
27
28
29 public final class CheckstyleValidator implements ResourceValidator {
30
31
32
33
34 private final Checker checker;
35
36
37
38
39 private final CheckstyleListener listener;
40
41
42
43
44 private final Environment env;
45
46
47
48
49
50 @SuppressWarnings("PMD.ConstructorOnlyInitializesOrCallOtherConstructors")
51 public CheckstyleValidator(final Environment env) {
52 this.env = env;
53 this.checker = new Checker();
54 this.checker.setModuleClassLoader(
55 Thread.currentThread().getContextClassLoader()
56 );
57 try {
58 this.checker.configure(this.configuration());
59 } catch (final CheckstyleException ex) {
60 throw new IllegalStateException("Failed to configure checker", ex);
61 }
62 this.listener = new CheckstyleListener(this.env);
63 this.checker.addListener(this.listener);
64 }
65
66 @Override
67 @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
68 public Collection<Violation> validate(final Collection<File> files) {
69 final List<File> sources = this.getNonExcludedFiles(files);
70 try {
71 this.checker.process(sources);
72 } catch (final CheckstyleException ex) {
73 throw new IllegalStateException("Failed to process files", ex);
74 }
75 final List<AuditEvent> events = this.listener.events();
76 final Collection<Violation> results = new LinkedList<>();
77 for (final AuditEvent event : events) {
78 final String check = event.getSourceName();
79 results.add(
80 new Violation.Default(
81 this.name(),
82 check.substring(check.lastIndexOf('.') + 1),
83 event.getFileName(),
84 String.valueOf(event.getLine()),
85 event.getMessage()
86 )
87 );
88 }
89 return results;
90 }
91
92 @Override public String name() {
93 return "Checkstyle";
94 }
95
96
97
98
99
100
101 public List<File> getNonExcludedFiles(final Collection<File> files) {
102 final List<File> relevant = new LinkedList<>();
103 for (final File file : files) {
104 final String name = file.getPath().substring(
105 this.env.basedir().toString().length()
106 );
107 if (this.env.exclude("checkstyle", name)) {
108 continue;
109 }
110 if (!name.toLowerCase(java.util.Locale.ROOT).endsWith(".java")) {
111 continue;
112 }
113 relevant.add(file);
114 }
115 return relevant;
116 }
117
118
119
120
121
122
123 private Configuration configuration() {
124 final File cache =
125 new File(this.env.tempdir(), "checkstyle/checkstyle.cache");
126 final File parent = cache.getParentFile();
127 if (!parent.exists() && !parent.mkdirs()) {
128 throw new IllegalStateException(
129 String.format(
130 "Unable to create directories needed for %s",
131 cache.getPath()
132 )
133 );
134 }
135 final Properties props = new Properties();
136 props.setProperty("cache.file", cache.getPath());
137 final Configuration config;
138 try (java.io.InputStream stream = this.getClass().getResourceAsStream("checks.xml")) {
139 if (stream == null) {
140 throw new IllegalStateException(
141 "Checkstyle configuration file 'checks.xml' not found in classpath."
142 );
143 }
144 final InputSource src = new InputSource(stream);
145 config = ConfigurationLoader.loadConfiguration(
146 src,
147 new PropertiesExpander(props),
148 ConfigurationLoader.IgnoredModulesOptions.OMIT
149 );
150 } catch (final CheckstyleException | java.io.IOException ex) {
151 throw new IllegalStateException("Failed to load config", ex);
152 }
153 return config;
154 }
155 }