/** * 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.internal.util.workarounds; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import org.mapstruct.ap.internal.version.VersionInformation; /** * Contains workarounds for various quirks in specific compilers. * * @author Sjaak Derksen * @author Andreas Gudian */ public class SpecificCompilerWorkarounds { private SpecificCompilerWorkarounds() { } /** * Tests whether one type is assignable to another, checking for VOID first. * * @param types the type utils * @param t1 the first type * @param t2 the second type * @return {@code true} if and only if the first type is assignable to the second * @throws IllegalArgumentException if given an executable or package type */ static boolean isAssignable(Types types, TypeMirror t1, TypeMirror t2) { if ( t1.getKind() == TypeKind.VOID ) { return false; } return types.isAssignable( t1, t2 ); } /** * Tests whether one type is a subtype of another. Any type is considered to be a subtype of itself. Also see <a * href="http://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html">JLS section 4.10, Subtyping</a>. * <p> * Work-around for a bug related to sub-typing in the Eclipse JSR 269 implementation. * * @param types the type utils * @param t1 the first type * @param t2 the second type * @return {@code true} if and only if the first type is a subtype of the second * @throws IllegalArgumentException if given an executable or package type */ static boolean isSubtype(Types types, TypeMirror t1, TypeMirror t2) { if ( t1.getKind() == TypeKind.VOID ) { return false; } return types.isSubtype( erasure( types, t1 ), erasure( types, t2 ) ); } /** * Returns the erasure of a type. * <p> * Performs an additional test on the given type to check if it is not void. Calling * {@link Types#erasure(TypeMirror)} with a void kind type will create a ClassCastException in Eclipse JDT. See the * JLS, section 4.6 Type Erasure, for reference. * * @param types the type utils * @param t the type to be erased * @return the erasure of the given type * @throws IllegalArgumentException if given a package type */ static TypeMirror erasure(Types types, TypeMirror t) { if ( t.getKind() == TypeKind.VOID || t.getKind() == TypeKind.NULL ) { return t; } else { return types.erasure( t ); } } /** * When running during Eclipse Incremental Compilation, we might get a TypeElement that has an UnresolvedTypeBinding * and which is not automatically resolved. In that case, getEnclosedElements returns an empty list. We take that as * a hint to check if the TypeElement resolved by FQN might have any enclosed elements and, if so, return the * resolved element. * * @param elementUtils element utils * @param element the original element * @return the element freshly resolved using the qualified name, if the original element did not return any * enclosed elements, whereas the resolved element does return enclosed elements. */ public static TypeElement replaceTypeElementIfNecessary(Elements elementUtils, TypeElement element) { if ( element.getEnclosedElements().isEmpty() ) { TypeElement resolvedByName = elementUtils.getTypeElement( element.getQualifiedName() ); if ( resolvedByName != null && !resolvedByName.getEnclosedElements().isEmpty() ) { return resolvedByName; } } return element; } /** * Workaround for Bugs in the Eclipse implementation of {@link Types#asMemberOf(DeclaredType, Element)}. * * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=382590">Eclipse Bug 382590 (fixed in Eclipse 4.6)</a> * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=481555">Eclipse Bug 481555</a> */ static TypeMirror asMemberOf(Types typeUtils, ProcessingEnvironment env, VersionInformation versionInformation, DeclaredType containing, Element element) { TypeMirror result = null; Exception lastException = null; try { try { result = typeUtils.asMemberOf( containing, element ); } catch ( IllegalArgumentException e ) { lastException = e; if ( versionInformation.isEclipseJDTCompiler() ) { result = EclipseClassLoaderBridge.invokeAsMemberOfWorkaround( env, containing, element ); } } } catch ( Exception e ) { lastException = e; } if ( null == result ) { throw new RuntimeException( "Fallback implementation of asMemberOf didn't work for " + element + " in " + containing, lastException ); } return result; } }