1
2
3
4
5 package com.qulice.checkstyle;
6
7 import com.jcabi.log.Logger;
8 import com.puppycrawl.tools.checkstyle.Checker;
9 import com.puppycrawl.tools.checkstyle.ConfigurationLoader;
10 import com.puppycrawl.tools.checkstyle.PropertiesExpander;
11 import com.puppycrawl.tools.checkstyle.api.AuditEvent;
12 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
13 import com.puppycrawl.tools.checkstyle.api.Configuration;
14 import com.qulice.spi.Environment;
15 import com.qulice.spi.Relative;
16 import com.qulice.spi.ResourceValidator;
17 import com.qulice.spi.Violation;
18 import java.io.File;
19 import java.util.Collection;
20 import java.util.LinkedList;
21 import java.util.List;
22 import java.util.Locale;
23 import java.util.Properties;
24 import java.util.Set;
25 import org.xml.sax.InputSource;
26
27
28
29
30
31
32 public final class CheckstyleValidator implements ResourceValidator {
33
34
35
36
37
38
39 private static final Set<String> EXTENSIONS = Set.of(
40 "java", "txt", "xml", "xsl", "xsd", "properties", "groovy", "vm",
41 "mf", "sh", "sql", "tokens", "g", "spec", "css", "csv", "js", "json",
42 "md", "yml", "yaml", "gradle", "dtd", "scss", "html"
43 );
44
45
46
47
48 private final Checker checker;
49
50
51
52
53 private final CheckstyleListener listener;
54
55
56
57
58 private final Environment env;
59
60
61
62
63
64 public CheckstyleValidator(final Environment env) {
65 this.env = env;
66 this.checker = new Checker();
67 this.listener = new CheckstyleListener(this.env);
68 }
69
70 @Override
71 public Collection<Violation> validate(final Collection<File> files) {
72 this.checker.setModuleClassLoader(
73 Thread.currentThread().getContextClassLoader()
74 );
75 try {
76 this.checker.configure(this.configuration());
77 } catch (final CheckstyleException ex) {
78 throw new IllegalStateException("Failed to configure checker", ex);
79 }
80 this.checker.addListener(this.listener);
81 final List<File> sources = this.getNonExcludedFiles(files);
82 final Collection<Violation> results = new LinkedList<>();
83 if (sources.isEmpty()) {
84 Logger.debug(
85 this,
86 "No files to check with Checkstyle, all %d are excluded",
87 files.size()
88 );
89 } else {
90 try {
91 Logger.debug(this, "Checkstyle processing %d files", sources.size());
92 this.checker.process(sources);
93 Logger.debug(this, "Checkstyle processed %d files", sources.size());
94 } catch (final CheckstyleException ex) {
95 throw new IllegalStateException("Failed to process files", ex);
96 }
97 for (final AuditEvent event : this.listener.events()) {
98 final String check = event.getSourceName();
99 results.add(
100 new Violation.Default(
101 this.name(),
102 check.substring(check.lastIndexOf('.') + 1),
103 event.getFileName(),
104 String.valueOf(event.getLine()),
105 event.getMessage()
106 )
107 );
108 }
109 }
110 return results;
111 }
112
113 @Override public String name() {
114 return "Checkstyle";
115 }
116
117
118
119
120
121
122 public List<File> getNonExcludedFiles(final Collection<File> files) {
123 final List<File> relevant = new LinkedList<>();
124 for (final File file : files) {
125 final String name = new Relative(this.env.basedir(), file).path();
126 if (this.env.exclude("checkstyle", name)) {
127 continue;
128 }
129 final int dot = name.lastIndexOf('.');
130 if (dot < 0) {
131 continue;
132 }
133 final String ext = name.substring(dot + 1).toLowerCase(Locale.ROOT);
134 if (!CheckstyleValidator.EXTENSIONS.contains(ext)) {
135 continue;
136 }
137 relevant.add(file);
138 }
139 return relevant;
140 }
141
142
143
144
145
146
147 private Configuration configuration() {
148 final File cache =
149 new File(this.env.tempdir(), "checkstyle/checkstyle.cache");
150 final File parent = cache.getParentFile();
151 if (!parent.exists() && !parent.mkdirs()) {
152 throw new IllegalStateException(
153 String.format(
154 "Unable to create directories needed for %s",
155 cache.getPath()
156 )
157 );
158 }
159 if (!parent.canWrite()) {
160 throw new IllegalStateException(
161 String.format(
162 "Cannot write to %s, check filesystem permissions",
163 parent.getAbsolutePath()
164 )
165 );
166 }
167 final Properties props = new Properties();
168 props.setProperty("cache.file", cache.getPath());
169 final Configuration config;
170 try (java.io.InputStream stream = this.getClass().getResourceAsStream("checks.xml")) {
171 if (stream == null) {
172 throw new IllegalStateException(
173 "Checkstyle configuration file 'checks.xml' not found in classpath."
174 );
175 }
176 config = ConfigurationLoader.loadConfiguration(
177 new InputSource(stream),
178 new PropertiesExpander(props),
179 ConfigurationLoader.IgnoredModulesOptions.OMIT
180 );
181 } catch (final CheckstyleException | java.io.IOException ex) {
182 throw new IllegalStateException("Failed to load config", ex);
183 }
184 return config;
185 }
186 }