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