View Javadoc
1   /*
2    * Copyright 2014-2015 Mark Prins, GeoDienstenCentrum.
3    * Copyright 2010-2014 Jasig.
4    *
5    * See the NOTICE file distributed with this work for additional information
6    * regarding copyright ownership.
7    *
8    * Licensed under the Apache License, Version 2.0 (the "License");
9    * you may not use this file except in compliance with the License.
10   * You may obtain a copy of the License at
11   *
12   * http://www.apache.org/licenses/LICENSE-2.0
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 nl.geodienstencentrum.maven.plugin.sass.compiler;
21  
22  import static org.apache.maven.plugins.annotations.LifecyclePhase.PROCESS_SOURCES;
23  import java.io.File;
24  import java.io.IOException;
25  import java.nio.file.FileSystems;
26  import java.nio.file.PathMatcher;
27  import java.util.Collection;
28  import nl.geodienstencentrum.maven.plugin.sass.AbstractSassMojo;
29  import org.apache.commons.io.DirectoryWalker;
30  import org.apache.maven.plugin.MojoExecutionException;
31  import org.apache.maven.plugin.MojoFailureException;
32  import org.apache.maven.plugins.annotations.Mojo;
33  
34  /**
35   * Mojo that compiles Sass sources into CSS files using
36   * {@code update_stylesheets}.
37   */
38  @Mojo(name = "update-stylesheets", defaultPhase = PROCESS_SOURCES, threadSafe = true)
39  public class UpdateStylesheetsMojo extends AbstractSassMojo {
40  
41  	/**
42  	 * Execute the compiler script.
43  	 *
44  	 * @see org.apache.maven.plugin.Mojo#execute()
45  	 * @throws MojoExecutionException when the execution of the plugin
46  	 *         errored
47  	 * @throws MojoFailureException when the Sass compilation fails
48  	 *
49  	 */
50  	@Override
51  	public void execute() throws MojoExecutionException, MojoFailureException {
52  		if (this.isSkip()) {
53  			this.getLog().info("Skip compiling Sass templates");
54  			return;
55  		}
56  		boolean buildRequired = true;
57  		try {
58  			buildRequired = buildRequired();
59  		} catch (IOException e) {
60  			throw new MojoExecutionException("Could not check file timestamps", e);
61  		}
62  		if (!buildRequired) {
63  			this.getLog().info("Skip compiling Sass templates, no changes.");
64  			return;
65  		}
66  
67  		this.getLog().info("Compiling Sass templates");
68  
69  		// build sass script
70  		final StringBuilder sassBuilder = new StringBuilder();
71  		this.buildBasicSassScript(sassBuilder);
72  		sassBuilder.append("Sass::Plugin.update_stylesheets");
73  		final String sassScript = sassBuilder.toString();
74  
75  		// ...and execute
76  		this.executeSassScript(sassScript);
77  	}
78  
79  	/**
80  	 * Returns true if a build is required.
81  	 *
82  	 * @return true if a build is required
83  	 * @throws IOException if one occurs checking the files and directories
84  	 */
85  	private boolean buildRequired() throws IOException {
86  		// If the target directory does not exist we need a build
87  		if (!buildDirectory.exists()) {
88  			return true;
89  		}
90  
91  		final LastModifiedWalker sourceWalker =
92  		        new LastModifiedWalker(getSassSourceDirectory());
93  		final FilteredLastModifiedWalker targetWalker = new FilteredLastModifiedWalker(destination);
94  		// If either directory is empty, we do a build to make sure
95  		if (sourceWalker.getCount() == 0 || targetWalker.getCount() == 0) {
96  			return true;
97  		}
98  
99  		return sourceWalker.getYoungest() > targetWalker.getYoungest();
100 	}
101 
102 	/**
103 	 * Directorywalker that looks at the lastModified timestamp of files.
104 	 *
105 	 * @see File#lastModified()
106 	 */
107 	private class FilteredLastModifiedWalker extends LastModifiedWalker {
108 		
109 		/**
110 		 * filter of the files.
111 		 */
112 		protected String filter;
113 		
114 		private PathMatcher pathMatcher;
115 		
116 		public FilteredLastModifiedWalker(File startDirectory) throws IOException {
117 			this(startDirectory, "**/*.css");
118 		}
119 		
120 		public FilteredLastModifiedWalker(File startDirectory, String filter) throws IOException {
121 			this.filter = filter;
122 			pathMatcher = FileSystems.getDefault().getPathMatcher("glob:" + this.filter );
123 			walk(startDirectory, null);
124 			getLog().info("Checked " + count + " filtered (" + this.filter + ") files for " + startDirectory);
125 		}
126 
127 		/**
128 		 * {@inheritDoc}
129 		 */
130 		@Override
131 		protected void handleFile(final File file, final int depth,
132 		        final Collection<Void> results) throws IOException {
133 			if ( pathMatcher.matches( file.toPath() )) {
134 				super.handleFile(file, depth, results);
135 			}
136 		}
137 	}
138 	
139 	/**
140 	 * Directorywalker that looks at the lastModified timestamp of files.
141 	 *
142 	 * @see File#lastModified()
143 	 */
144 	private class LastModifiedWalker extends DirectoryWalker<Void> {
145 		/**
146 		 * timestamp of the youngest file.
147 		 */
148 		protected Long youngest;
149 		/**
150 		 * timestamp of the oldest file.
151 		 */
152 		protected Long oldest;
153 		/**
154 		 * number of files in the directory.
155 		 */
156 		protected int count = 0;
157 
158 		public LastModifiedWalker(){}
159 		/**
160 		 * Create a "last modified" directory walker.
161 		 * @param startDirectory The direcoty to start walking.
162 		 * @throws IOException if any occurs while walking the directory tree.
163 		 */
164 		public LastModifiedWalker(final File startDirectory) throws IOException {
165 			walk(startDirectory, null);
166 			getLog().info("Checked " + count + " files for " + startDirectory);
167 		}
168 
169 		/**
170 		 * {@inheritDoc}
171 		 */
172 		@Override
173 		protected void handleFile(final File file, final int depth,
174 		        final Collection<Void> results) throws IOException {
175 			long lastMod = file.lastModified();
176 			// CHECKSTYLE:OFF:AvoidInlineConditionals
177 			youngest = (youngest == null ? lastMod : Math.max(youngest, lastMod));
178 			oldest = (oldest == null ? lastMod : Math.min(oldest, lastMod));
179 			// CHECKSTYLE:ON
180 			count++;
181 			super.handleFile(file, depth, results);
182 		}
183 
184 		/**
185 		 * Get timestamp of the youngest file in the directory.
186 		 *
187 		 * @return timestamp of youngest file
188 		 * @see File#lastModified()
189 		 */
190 		public Long getYoungest() {
191 			return youngest;
192 		}
193 
194 		/**
195 		 * Get timestamp of the oldest file in the directory.
196 		 *
197 		 * @return timestamp of oldest file
198 		 * @see File#lastModified()
199 		 */
200 		public Long getOldest() {
201 			return oldest;
202 		}
203 
204 		/**
205 		 * get number of files in the directory.
206 		 *
207 		 * @return number of files
208 		 */
209 		public int getCount() {
210 			return count;
211 		}
212 	}
213 }