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