/** * Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/) * and/or other contributors as indicated by the @authors tag. See the * copyright.txt file in the distribution for a full listing of all * contributors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.mapstruct.ap.testutil.assertions; import static java.lang.String.format; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.assertj.core.api.AbstractCharSequenceAssert; import org.assertj.core.api.Assertions; import org.assertj.core.api.FileAssert; import org.assertj.core.api.exception.RuntimeIOException; import org.assertj.core.error.ShouldHaveSameContent; import org.assertj.core.internal.Diff; import org.assertj.core.internal.Failures; import org.assertj.core.util.diff.Delta; import com.google.common.base.Charsets; import com.google.common.io.Files; /** * Allows to perform assertions on .java source files. * * @author Andreas Gudian */ public class JavaFileAssert extends FileAssert { private static final String FIRST_LINE_LICENSE_REGEX = ".*Copyright 2012-\\d{4} Gunnar Morling.*"; private static final String GENERATED_DATE_REGEX = "\\s+date = " + "\"\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\+\\d{4}\","; private static final String GENERATED_COMMENTS_REGEX = "\\s+comments = \"version: , compiler: .*, environment: " + ".*\""; private Diff diff = new Diff(); /** * @param actual the actual file */ public JavaFileAssert(File actual) { super( actual ); } /** * @return assertion on the file content */ public AbstractCharSequenceAssert<?, String> content() { exists(); isFile(); try { return Assertions.assertThat( Files.toString( actual, Charsets.UTF_8 ) ); } catch ( IOException e ) { failWithMessage( "Unable to read" + actual.toString() + ". Exception: " + e.getMessage() ); } return null; } /** * Verifies that the specified class is imported in this Java file * * @param importedClass the class expected to be imported in this Java file */ public void containsImportFor(Class<?> importedClass) { content().contains( getClassImportDeclaration( importedClass ) ); } /** * Verifies that the specified class is not imported in this Java file * * @param importedClass the class expected not to be imported in this Java file */ public void containsNoImportFor(Class<?> importedClass) { content().doesNotContain( getClassImportDeclaration( importedClass ) ); } /** * Verifies that the expected file has the same content as this Java file. The verification ignores * the license header and the date/comments line from the {@code @Generated} annotation. * * @param expected the file that should be matched */ public void hasSameMapperContent(File expected) { Charset charset = Charset.forName( "UTF-8" ); try { List<Delta<String>> diffs = new ArrayList<Delta<String>>( this.diff.diff( actual, charset, expected, charset ) ); Iterator<Delta<String>> iterator = diffs.iterator(); while ( iterator.hasNext() ) { Delta<String> delta = iterator.next(); if ( ignoreDelta( delta ) ) { iterator.remove(); } } if ( !diffs.isEmpty() ) { throw Failures.instance() .failure( info, ShouldHaveSameContent.shouldHaveSameContent( actual, expected, diffs ) ); } } catch ( IOException e ) { throw new RuntimeIOException( format( "Unable to compare contents of files:<%s> and:<%s>", actual, expected ), e ); } } /** * Checks if the delta should be ignored. The delta is ignored if it is a deletion type for the license header * or if it is a change delta for the date/comments part of a {@code @Generated} annotation. * * @param delta that needs to be checked * * @return {@code true} if this delta should be ignored, {@code false} otherwise */ private boolean ignoreDelta(Delta<String> delta) { if ( delta.getType() == Delta.TYPE.DELETE ) { List<String> lines = delta.getOriginal().getLines(); return lines.size() > 2 && lines.get( 1 ).matches( FIRST_LINE_LICENSE_REGEX ); } else if ( delta.getType() == Delta.TYPE.CHANGE ) { List<String> lines = delta.getOriginal().getLines(); if ( lines.size() == 1 ) { return lines.get( 0 ).matches( GENERATED_DATE_REGEX ); } else if ( lines.size() == 2 ) { return lines.get( 0 ).matches( GENERATED_DATE_REGEX ) && lines.get( 1 ).matches( GENERATED_COMMENTS_REGEX ); } } return false; } /** * Build a class import declaration string. * * @param importedClass * @return */ private String getClassImportDeclaration(Class<?> importedClass) { String classname = importedClass.getName(); if ( importedClass.isMemberClass() ) { // Member-Class name: a.b.Outer$Inner // Import declaration: import a.b.Outer.Inner classname = classname.replace( '$', '.' ); } return "import " + classname + ";"; } }