001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.util.license; 018 019import java.io.File; 020import java.io.FileFilter; 021import java.util.ArrayList; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.Map.Entry; 026import java.util.Objects; 027 028import org.apache.wicket.util.lang.Generics; 029import org.apache.wicket.util.string.Strings; 030import org.slf4j.Logger; 031import org.slf4j.LoggerFactory; 032 033/** 034 * Testcase used in the different wicket projects for testing for the correct ASL license headers. 035 * Doesn't really make sense outside org.apache.wicket. 036 * 037 * @author Frank Bille Jensen (frankbille) 038 */ 039public abstract class ApacheLicenseHeaderTestCase 040{ 041 /** Log. */ 042 private static final Logger log = LoggerFactory.getLogger(ApacheLicenseHeaderTestCase.class); 043 044 private static final String LINE_ENDING = System.getProperty("line.separator"); 045 protected List<String> javaIgnore = Generics.newArrayList(); 046 protected List<String> htmlIgnore = Generics.newArrayList(); 047 protected List<String> xmlPrologIgnore = Generics.newArrayList(); 048 protected List<String> propertiesIgnore = Generics.newArrayList(); 049 protected List<String> xmlIgnore = Generics.newArrayList(); 050 protected List<String> cssIgnore = Generics.newArrayList(); 051 protected List<String> velocityIgnore = Generics.newArrayList(); 052 protected List<String> javaScriptIgnore = Generics.newArrayList(); 053 protected boolean addHeaders = false; 054 private ILicenseHeaderHandler[] licenseHeaderHandlers; 055 private File baseDirectory = new File("").getAbsoluteFile(); 056 /** 057 * Construct. 058 */ 059 public ApacheLicenseHeaderTestCase() 060 { 061 062 // ------------------------------- 063 // Configure defaults 064 // ------------------------------- 065 066 // addHeaders = true; 067 xmlIgnore.add(".settings"); 068 xmlIgnore.add("EclipseCodeFormat.xml"); 069 xmlIgnore.add("nb-configuration.xml"); 070 071 /* 072 * License header in test files lower the visibility of the test. 073 */ 074 htmlIgnore.add("src/test/java"); 075 076 /* 077 * Low level configuration files for logging. No license needed. 078 */ 079 propertiesIgnore.add("src/test/java"); 080 081 /* 082 * .html in test is very test specific and a license header would confuse and make it 083 * unclear what the test is about. 084 */ 085 xmlPrologIgnore.add("src/test/java"); 086 087 /* 088 * Ignore package.html 089 */ 090 xmlPrologIgnore.add("package.html"); 091 } 092 093 /** 094 * 095 */ 096 protected void before() 097 { 098 // setup the base directory for when running inside maven (building a release 099 // comes to mind). 100 String property = System.getProperty("basedir"); 101 if (!Strings.isEmpty(property)) 102 { 103 baseDirectory = new File(property).getAbsoluteFile(); 104 } 105 } 106 107 /** 108 * Test all the files in the project which has an associated {@link ILicenseHeaderHandler}. 109 */ 110 protected void licenseHeaders() 111 { 112 licenseHeaderHandlers = new ILicenseHeaderHandler[] { 113 new JavaLicenseHeaderHandler(javaIgnore), 114 new JavaScriptLicenseHeaderHandler(javaScriptIgnore), 115 new XmlLicenseHeaderHandler(xmlIgnore), 116 new PropertiesLicenseHeaderHandler(propertiesIgnore), 117 new HtmlLicenseHeaderHandler(htmlIgnore), 118 new VelocityLicenseHeaderHandler(velocityIgnore), 119 new XmlPrologHeaderHandler(xmlPrologIgnore), 120 new CssLicenseHeaderHandler(cssIgnore), }; 121 122 final Map<ILicenseHeaderHandler, List<File>> badFiles = new HashMap<>(); 123 124 for (final ILicenseHeaderHandler licenseHeaderHandler : licenseHeaderHandlers) 125 { 126 visitFiles(licenseHeaderHandler.getSuffixes(), licenseHeaderHandler.getIgnoreFiles(), 127 new FileVisitor() 128 { 129 @Override 130 public void visitFile(final File file) 131 { 132 if (licenseHeaderHandler.checkLicenseHeader(file) == false) 133 { 134 if ((addHeaders == false) || 135 (licenseHeaderHandler.addLicenseHeader(file) == false)) 136 { 137 List<File> files = badFiles.get(licenseHeaderHandler); 138 139 if (files == null) 140 { 141 files = new ArrayList<>(); 142 badFiles.put(licenseHeaderHandler, files); 143 } 144 145 files.add(file); 146 } 147 } 148 } 149 }); 150 } 151 152 failIncorrectLicenceHeaders(badFiles); 153 } 154 155 private void failIncorrectLicenceHeaders(final Map<ILicenseHeaderHandler, List<File>> files) 156 { 157 if (files.size() > 0) 158 { 159 StringBuilder failString = new StringBuilder(); 160 161 for (Entry<ILicenseHeaderHandler, List<File>> entry : files.entrySet()) 162 { 163 ILicenseHeaderHandler licenseHeaderHandler = entry.getKey(); 164 List<File> fileList = entry.getValue(); 165 166 failString.append('\n'); 167 failString.append(licenseHeaderHandler.getClass().getName()); 168 failString.append(" failed. The following files("); 169 failString.append(fileList.size()); 170 failString.append(") didn't have correct license header:\n"); 171 172 for (File file : fileList) 173 { 174 String filename = file.getAbsolutePath(); 175 176 // Find the license type 177 String licenseType = licenseHeaderHandler.getLicenseType(file); 178 179 failString.append(Objects.requireNonNullElse(licenseType, "NONE")); 180 failString.append(' ').append(filename).append(LINE_ENDING); 181 } 182 } 183 184 System.out.println(failString); 185 throw new AssertionError(failString.toString()); 186 } 187 } 188 189 private void visitFiles(final List<String> suffixes, final List<String> ignoreFiles, 190 final FileVisitor fileVisitor) 191 { 192 visitDirectory(suffixes, ignoreFiles, baseDirectory, fileVisitor); 193 } 194 195 private void visitDirectory(final List<String> suffixes, final List<String> ignoreFiles, 196 final File directory, final FileVisitor fileVisitor) 197 { 198 File[] files = directory.listFiles(new SuffixAndIgnoreFileFilter(suffixes, ignoreFiles)); 199 200 if (files != null) 201 { 202 for (File file : files) 203 { 204 fileVisitor.visitFile(file); 205 } 206 } 207 208 // Find the directories in this directory on traverse deeper 209 files = directory.listFiles(new DirectoryFileFilter()); 210 211 if (files != null) 212 { 213 for (File childDirectory : files) 214 { 215 visitDirectory(suffixes, ignoreFiles, childDirectory, fileVisitor); 216 } 217 } 218 } 219 220 interface FileVisitor 221 { 222 /** 223 * @param file 224 */ 225 void visitFile(File file); 226 } 227 228 private class SuffixAndIgnoreFileFilter implements FileFilter 229 { 230 private final List<String> suffixes; 231 private final List<String> ignoreFiles; 232 233 private SuffixAndIgnoreFileFilter(final List<String> suffixes, 234 final List<String> ignoreFiles) 235 { 236 this.suffixes = suffixes; 237 this.ignoreFiles = ignoreFiles; 238 } 239 240 @Override 241 public boolean accept(final File pathname) 242 { 243 boolean accept = false; 244 245 if (pathname.isFile()) 246 { 247 if (ignoreFile(pathname) == false) 248 { 249 for (String suffix : suffixes) 250 { 251 if (pathname.getName().endsWith("." + suffix)) 252 { 253 accept = true; 254 break; 255 } 256 else 257 { 258 log.debug("File ignored: '{}'", pathname); 259 } 260 } 261 } 262 else 263 { 264 log.debug("File ignored: '{}'", pathname); 265 } 266 } 267 268 return accept; 269 } 270 271 private boolean ignoreFile(final File pathname) 272 { 273 boolean ignore = false; 274 275 if (ignoreFiles != null) 276 { 277 String relativePathname = pathname.getAbsolutePath(); 278 relativePathname = Strings 279 .replaceAll(relativePathname, 280 baseDirectory.getAbsolutePath() + System.getProperty("file.separator"), "") 281 .toString(); 282 283 for (String ignorePath : ignoreFiles) 284 { 285 // Will convert '/'s to '\\'s on Windows 286 ignorePath = Strings 287 .replaceAll(ignorePath, "/", System.getProperty("file.separator")) 288 .toString(); 289 File ignoreFile = new File(baseDirectory, ignorePath); 290 291 // Directory ignore 292 if (ignoreFile.isDirectory()) 293 { 294 if (pathname.getAbsolutePath().startsWith(ignoreFile.getAbsolutePath())) 295 { 296 ignore = true; 297 break; 298 } 299 } 300 // Absolute file 301 else if (ignoreFile.isFile()) 302 { 303 if (relativePathname.equals(ignorePath)) 304 { 305 ignore = true; 306 break; 307 } 308 } 309 else if (pathname.getName().equals(ignorePath)) 310 { 311 ignore = true; 312 break; 313 } 314 } 315 } 316 317 return ignore; 318 } 319 } 320 321 private class DirectoryFileFilter implements FileFilter 322 { 323 private final String[] ignoreDirectory = new String[] { ".git" }; 324 325 @Override 326 public boolean accept(final File pathname) 327 { 328 boolean accept = false; 329 330 if (pathname.isDirectory()) 331 { 332 String relativePathname = pathname.getAbsolutePath(); 333 relativePathname = Strings 334 .replaceAll(relativePathname, 335 baseDirectory.getAbsolutePath() + System.getProperty("file.separator"), "") 336 .toString(); 337 if ("target".equals(relativePathname) == false) 338 { 339 boolean found = false; 340 for (String ignore : ignoreDirectory) 341 { 342 if (pathname.getName().equals(ignore)) 343 { 344 found = true; 345 break; 346 } 347 } 348 if (found == false) 349 { 350 accept = true; 351 } 352 } 353 } 354 355 return accept; 356 } 357 } 358}