View Javadoc
1   /*
2    * SPDX-License-Identifier: LGPL-2.1-or-later
3    * See LICENSE file for details.
4    *
5    * Copyright 2012-2026 Hazendaz.
6    */
7   package net.alchim31.maven.yuicompressor;
8   
9   import java.io.File;
10  import java.io.IOException;
11  import java.io.InputStream;
12  import java.io.OutputStream;
13  import java.nio.charset.StandardCharsets;
14  import java.nio.file.Files;
15  import java.nio.file.Path;
16  import java.util.ArrayList;
17  import java.util.Arrays;
18  import java.util.Collection;
19  import java.util.List;
20  import java.util.Set;
21  
22  import org.codehaus.plexus.build.BuildContext;
23  import org.codehaus.plexus.util.DirectoryScanner;
24  import org.codehaus.plexus.util.IOUtil;
25  
26  /**
27   * The Class Aggregation.
28   */
29  public class Aggregation {
30  
31      /** The input dir. */
32      private File inputDir;
33  
34      /** The output. */
35      private File output;
36  
37      /** The includes. */
38      private String[] includes;
39  
40      /** The excludes. */
41      private String[] excludes;
42  
43      /** The remove included. */
44      private boolean removeIncluded;
45  
46      /** The insert new line. */
47      private boolean insertNewLine;
48  
49      /** The insert file header. */
50      private boolean insertFileHeader;
51  
52      /** The fix last semicolon. */
53      private boolean fixLastSemicolon;
54  
55      /** The auto exclude wildcards. */
56      private boolean autoExcludeWildcards;
57  
58      /**
59       * Gets the output.
60       *
61       * @return the output
62       */
63      public File getOutput() {
64          return output;
65      }
66  
67      /**
68       * Sets the output.
69       *
70       * @param output
71       *            the new output
72       */
73      public void setOutput(File output) {
74          this.output = output;
75      }
76  
77      /**
78       * Sets the includes.
79       *
80       * @param includes
81       *            the new includes
82       */
83      public void setIncludes(String[] includes) {
84          this.includes = includes;
85      }
86  
87      /**
88       * Sets the insert new line.
89       *
90       * @param insertNewLine
91       *            the new insert new line
92       */
93      public void setInsertNewLine(boolean insertNewLine) {
94          this.insertNewLine = insertNewLine;
95      }
96  
97      /**
98       * Sets the auto exclude wildcards.
99       *
100      * @param autoExcludeWildcards
101      *            the new auto exclude wildcards
102      */
103     public void setAutoExcludeWildcards(boolean autoExcludeWildcards) {
104         this.autoExcludeWildcards = autoExcludeWildcards;
105     }
106 
107     /**
108      * Run.
109      *
110      * @param previouslyIncludedFiles
111      *            the previously included files
112      * @param buildContext
113      *            the build context
114      *
115      * @return the list
116      *
117      * @throws IOException
118      *             the IO exception
119      */
120     public List<File> run(Collection<File> previouslyIncludedFiles, BuildContext buildContext) throws IOException {
121         return this.run(previouslyIncludedFiles, buildContext, null);
122     }
123 
124     /**
125      * Run.
126      *
127      * @param previouslyIncludedFiles
128      *            the previously included files
129      * @param buildContext
130      *            the build context
131      * @param incrementalFiles
132      *            the incremental files
133      *
134      * @return the list
135      *
136      * @throws IOException
137      *             the IO exception
138      */
139     public List<File> run(Collection<File> previouslyIncludedFiles, BuildContext buildContext,
140             Set<String> incrementalFiles) throws IOException {
141         defineInputDir();
142 
143         List<File> files;
144         if (autoExcludeWildcards) {
145             files = getIncludedFiles(previouslyIncludedFiles, buildContext, incrementalFiles);
146         } else {
147             files = getIncludedFiles(null, buildContext, incrementalFiles);
148         }
149 
150         if (!files.isEmpty()) {
151             output = output.getCanonicalFile();
152             output.getParentFile().mkdirs();
153             try (OutputStream out = buildContext.newFileOutputStream(output)) {
154                 for (File file : files) {
155                     if (file.getCanonicalPath().equals(output.getCanonicalPath())) {
156                         continue;
157                     }
158                     try (InputStream in = Files.newInputStream(file.toPath())) {
159                         if (insertFileHeader) {
160                             out.write(createFileHeader(file).getBytes(StandardCharsets.UTF_8));
161                         }
162                         IOUtil.copy(in, out);
163                         if (fixLastSemicolon) {
164                             out.write(';');
165                         }
166                         if (insertNewLine) {
167                             out.write('\n');
168                         }
169                     }
170                     if (removeIncluded) {
171                         if (file.exists()) {
172                             Files.delete(file.toPath());
173                         }
174                         buildContext.refresh(file);
175                     }
176                 }
177             }
178         }
179         return files;
180     }
181 
182     /**
183      * Creates the file header.
184      *
185      * @param file
186      *            the file
187      *
188      * @return the string
189      */
190     private String createFileHeader(File file) {
191         StringBuilder header = new StringBuilder();
192         header.append("/*");
193         header.append(file.getName());
194         header.append("*/");
195 
196         if (insertNewLine) {
197             header.append('\n');
198         }
199 
200         return header.toString();
201     }
202 
203     /**
204      * Define input dir.
205      *
206      * @throws IOException
207      *             the exception
208      */
209     private void defineInputDir() throws IOException {
210         if (inputDir == null) {
211             inputDir = output.getParentFile();
212         }
213         inputDir = inputDir.getCanonicalFile();
214         if (!inputDir.isDirectory()) {
215             throw new IllegalStateException("input directory not found: " + inputDir);
216         }
217     }
218 
219     /**
220      * Gets the included files.
221      *
222      * @param previouslyIncludedFiles
223      *            the previously included files
224      * @param buildContext
225      *            the build context
226      * @param incrementalFiles
227      *            the incremental files
228      *
229      * @return the included files
230      *
231      * @throws IOException
232      *             the IO exception
233      */
234     private List<File> getIncludedFiles(Collection<File> previouslyIncludedFiles, BuildContext buildContext,
235             Set<String> incrementalFiles) throws IOException {
236         List<File> filesToAggregate = new ArrayList<>();
237         if (includes != null) {
238             for (String include : includes) {
239                 addInto(include, filesToAggregate, previouslyIncludedFiles);
240             }
241         }
242 
243         // If build is incremental with no delta, then don't include for aggregation
244         if (!buildContext.isIncremental()) {
245             return filesToAggregate;
246         }
247         if (incrementalFiles != null) {
248             boolean aggregateMustBeUpdated = false;
249             for (File file : filesToAggregate) {
250                 if (incrementalFiles.contains(file.getCanonicalPath())) {
251                     aggregateMustBeUpdated = true;
252                     break;
253                 }
254             }
255 
256             if (aggregateMustBeUpdated) {
257                 return filesToAggregate;
258             }
259         }
260         return new ArrayList<>();
261 
262     }
263 
264     /**
265      * Adds the into.
266      *
267      * @param include
268      *            the include
269      * @param includedFiles
270      *            the included files
271      * @param previouslyIncludedFiles
272      *            the previously included files
273      */
274     private void addInto(String include, List<File> includedFiles, Collection<File> previouslyIncludedFiles) {
275         if (include.indexOf('*') > -1) {
276             DirectoryScanner scanner = newScanner();
277             scanner.setIncludes(new String[] { include });
278             scanner.scan();
279             String[] rpaths = scanner.getIncludedFiles();
280             Arrays.sort(rpaths);
281             for (String rpath : rpaths) {
282                 File file = scanner.getBasedir().toPath().resolve(rpath).toFile();
283                 if (!includedFiles.contains(file)
284                         && (previouslyIncludedFiles == null || !previouslyIncludedFiles.contains(file))) {
285                     includedFiles.add(file);
286                 }
287             }
288         } else {
289             File file = Path.of(include).toFile();
290             if (!file.isAbsolute()) {
291                 file = inputDir.toPath().resolve(include).toFile();
292             }
293             if (!includedFiles.contains(file)) {
294                 includedFiles.add(file);
295             }
296         }
297     }
298 
299     /**
300      * New scanner.
301      *
302      * @return the directory scanner
303      */
304     private DirectoryScanner newScanner() {
305         DirectoryScanner scanner = new DirectoryScanner();
306         scanner.setBasedir(inputDir);
307         if (excludes != null && excludes.length != 0) {
308             scanner.setExcludes(excludes);
309         }
310         scanner.addDefaultExcludes();
311         return scanner;
312     }
313 }