View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2026 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.qulice.spi;
6   
7   import java.io.File;
8   import java.io.IOException;
9   import java.nio.charset.Charset;
10  import java.nio.charset.StandardCharsets;
11  import java.nio.file.Files;
12  import java.util.Arrays;
13  import java.util.Collection;
14  import java.util.Collections;
15  import java.util.HashMap;
16  import java.util.HashSet;
17  import java.util.LinkedList;
18  import java.util.Map;
19  import java.util.Set;
20  import org.apache.commons.io.FileUtils;
21  import org.apache.commons.io.filefilter.DirectoryFileFilter;
22  import org.apache.commons.io.filefilter.IOFileFilter;
23  import org.apache.commons.io.filefilter.WildcardFileFilter;
24  
25  /**
26   * Environment.
27   * @since 0.3
28   */
29  @SuppressWarnings("PMD.TooManyMethods")
30  public interface Environment {
31  
32      /**
33       * Get project's basedir.
34       * @return The directory
35       */
36      File basedir();
37  
38      /**
39       * Get directory to keep temporary files in.
40       * @return The directory
41       */
42      File tempdir();
43  
44      /**
45       * Get directory where <tt>.class</tt> files are stored.
46       * @return The directory
47       */
48      File outdir();
49  
50      /**
51       * Get parameter by name, and return default if it's not set.
52       * @param name The name of parameter
53       * @param value Default value to return as default
54       * @return The value
55       */
56      String param(String name, String value);
57  
58      /**
59       * Get classloader for this project.
60       * @return The classloader
61       */
62      ClassLoader classloader();
63  
64      /**
65       * Get list of paths in classpath.
66       * @return The collection of paths
67       */
68      Collection<String> classpath();
69  
70      /**
71       * Returns the files matching the specified pattern.
72       *
73       * <p>The pattern matching scheme used is wildcard matching. The characters
74       * '?' and '*' represents single or multiple wildcard characters,
75       * respectively. Pattern matching is case sensitive.
76       *
77       * @param pattern File name pattern
78       * @return Collection of files, matching the specified pattern
79       */
80      Collection<File> files(String pattern);
81  
82      /**
83       * Shall this item be excluded from report?
84       * @param check Name of the check that is asking
85       * @param name File or any other item, which is subject of validation
86       * @return TRUE if it should be ignored
87       */
88      boolean exclude(String check, String name);
89  
90      /**
91       * List of exclude patterns for given checker.
92       * Each list element will contain exactly one exclude pattern which,
93       * depending on the plugin that uses the excludes might be either wildcard
94       * (CodeNarc) pattern or regex pattern (FindBugs).
95       * @param checker Name of the checker that is asking (pmd, codenarc ...)
96       * @return Exclude patterns
97       */
98      Collection<String> excludes(String checker);
99  
100     /**
101      * Encoding for the files.
102      * @return Source files charset
103      */
104     Charset encoding();
105 
106     /**
107      * Mock of {@link Environment}.
108      * @since 0.1
109      */
110     final class Mock implements Environment {
111 
112         /**
113          * The basedir.
114          */
115         private final File origin = ((java.util.function.Supplier<File>) () -> {
116             try {
117                 final File temp = Files.createTempDirectory("mock-qulice").toFile();
118                 FileUtils.forceDeleteOnExit(temp);
119                 final File base = new File(temp, "basedir");
120                 if (!base.mkdirs()) {
121                     throw new IllegalStateException(
122                         String.format("cannot create basedir at %s", base)
123                     );
124                 }
125                 return base;
126             } catch (final IOException ex) {
127                 throw new IllegalStateException("cannot create basedir", ex);
128             }
129         }).get();
130 
131         /**
132          * Files for classpath.
133          */
134         private final Set<String> paths = new HashSet<>(
135             Collections.singleton(
136                 ((java.util.function.Supplier<String>) () -> {
137                     final File out = new File(this.origin, "target/classes");
138                     if (!out.mkdirs() && !out.isDirectory()) {
139                         throw new IllegalStateException(
140                             String.format("cannot create classes dir at %s", out)
141                         );
142                     }
143                     return out.getAbsolutePath()
144                         .replace(File.separatorChar, '/');
145                 }).get()
146             )
147         );
148 
149         /**
150          * Map of params.
151          */
152         private final Map<String, String> params = new HashMap<>();
153 
154         /**
155          * Exclude patterns.
156          */
157         private String excl;
158 
159         /**
160          * With this param and its value.
161          * @param name Param name
162          * @param value Param value
163          * @return This object
164          */
165         public Environment.Mock withParam(final String name,
166             final String value) {
167             this.params.put(name, value);
168             return this;
169         }
170 
171         /**
172          * With this file on board.
173          * @param name File name related to basedir
174          * @param content File content to write
175          * @return This object
176          * @throws IOException If some IO problem
177          */
178         public Environment.Mock withFile(final String name,
179             final String content) throws IOException {
180             FileUtils.writeStringToFile(
181                 new File(this.origin, name),
182                 content,
183                 StandardCharsets.UTF_8
184             );
185             return this;
186         }
187 
188         /**
189          * With this file on board.
190          * @param name File name related to basedir
191          * @param bytes File content to write
192          * @return This object
193          * @throws IOException If some IO problem
194          */
195         public Environment.Mock withFile(final String name,
196             final byte[] bytes) throws IOException {
197             FileUtils.writeByteArrayToFile(new File(this.origin, name), bytes);
198             return this;
199         }
200 
201         /**
202          * With exclude patterns.
203          * @param excludes Exclude patterns
204          * @return This object
205          */
206         public Environment.Mock withExcludes(final String excludes) {
207             this.excl = excludes;
208             return this;
209         }
210 
211         /**
212          * With default classpath.
213          * @return This object
214          */
215         public Environment.Mock withDefaultClasspath() {
216             Collections.addAll(
217                 this.paths,
218                 System.getProperty("java.class.path")
219                     .split(System.getProperty("path.separator"))
220             );
221             return this;
222         }
223 
224         @Override
225         public File basedir() {
226             return this.origin;
227         }
228 
229         @Override
230         public File tempdir() {
231             final File file = new File(this.origin, "target/tempdir");
232             if (!file.mkdirs() && !file.isDirectory()) {
233                 throw new IllegalStateException(
234                     String.format("cannot create tempdir at %s", file)
235                 );
236             }
237             return file;
238         }
239 
240         @Override
241         public File outdir() {
242             final File file = new File(this.origin, "target/classes");
243             if (!file.mkdirs() && !file.isDirectory()) {
244                 throw new IllegalStateException(
245                     String.format("cannot create outdir at %s", file)
246                 );
247             }
248             return file;
249         }
250 
251         @Override
252         public String param(final String name, final String value) {
253             String val = this.params.get(name);
254             if (val == null) {
255                 val = value;
256             }
257             return val;
258         }
259 
260         @Override
261         public ClassLoader classloader() {
262             return Thread.currentThread().getContextClassLoader();
263         }
264 
265         @Override
266         public Collection<String> classpath() {
267             return Collections.unmodifiableCollection(this.paths);
268         }
269 
270         @Override
271         public Collection<File> files(final String pattern) {
272             final Collection<File> files = new LinkedList<>();
273             final IOFileFilter filter = WildcardFileFilter.builder().setWildcards(pattern).get();
274             if (this.basedir().exists()) {
275                 for (final File found : FileUtils.listFiles(
276                     this.basedir(),
277                     filter,
278                     DirectoryFileFilter.INSTANCE
279                 )) {
280                     if (!new Binary(found).yes()) {
281                         files.add(found);
282                     }
283                 }
284             }
285             return files;
286         }
287 
288         @Override
289         public boolean exclude(final String check, final String name) {
290             return false;
291         }
292 
293         @Override
294         public Collection<String> excludes(final String checker) {
295             final Collection<String> exc;
296             if (this.excl == null) {
297                 exc = Collections.emptyList();
298             } else {
299                 exc = Arrays.asList(this.excl.split(","));
300             }
301             return exc;
302         }
303 
304         @Override
305         public Charset encoding() {
306             return StandardCharsets.UTF_8;
307         }
308     }
309 }