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}