diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactory.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactory.java index 649c3c7a7..dddd6d685 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactory.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactory.java @@ -25,6 +25,7 @@ import java.text.SimpleDateFormat; import org.mapstruct.ap.internal.util.JavaTimeConstants; import org.mapstruct.ap.internal.util.JodaTimeConstants; import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.XmlConstants; /** * Factory for {@link DateFormatValidator}.

Based on the types of source / target type a specific {@link @@ -42,7 +43,6 @@ final class DateFormatValidatorFactory { private static final String ORG_JODA_TIME_FORMAT_DATE_TIME_FORMAT = "org.joda.time.format.DateTimeFormat"; private static final String FOR_PATTERN = "forPattern"; - private static final String JAVAX_XML_DATATYPE_XMLGREGORIAN_CALENDAR = "javax.xml.datatype.XMLGregorianCalendar"; private DateFormatValidatorFactory() { } @@ -85,7 +85,7 @@ final class DateFormatValidatorFactory { private static boolean isXmlGregorianCalendarSupposedToBeMapped(Type sourceType, Type targetType) { return typesEqualsOneOf( - sourceType, targetType, JAVAX_XML_DATATYPE_XMLGREGORIAN_CALENDAR ); + sourceType, targetType, XmlConstants.JAVAX_XML_DATATYPE_XMLGREGORIAN_CALENDAR ); } private static boolean isJodaDateTimeSupposed(Type sourceType, Type targetType) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMappingMethods.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMappingMethods.java index 87de5c13c..9675bf386 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMappingMethods.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMappingMethods.java @@ -18,13 +18,14 @@ */ package org.mapstruct.ap.internal.model.source.builtin; +import java.util.ArrayList; import java.util.List; import org.mapstruct.ap.internal.model.common.TypeFactory; -import org.mapstruct.ap.internal.util.Collections; import org.mapstruct.ap.internal.util.JavaTimeConstants; import org.mapstruct.ap.internal.util.JaxbConstants; import org.mapstruct.ap.internal.util.JodaTimeConstants; +import org.mapstruct.ap.internal.util.XmlConstants; /** * Registry for all built-in methods. @@ -36,27 +37,32 @@ public class BuiltInMappingMethods { private final List builtInMethods; public BuiltInMappingMethods(TypeFactory typeFactory) { - builtInMethods = Collections.newArrayList( - new DateToXmlGregorianCalendar( typeFactory ), - new XmlGregorianCalendarToDate( typeFactory ), - new StringToXmlGregorianCalendar( typeFactory ), - new XmlGregorianCalendarToString( typeFactory ), - new CalendarToXmlGregorianCalendar( typeFactory ), - new XmlGregorianCalendarToCalendar( typeFactory ) - ); + boolean isXmlGregorianCalendarPresent = isXmlGregorianCalendarAvailable( typeFactory ); + builtInMethods = new ArrayList( 20 ); + if ( isXmlGregorianCalendarPresent ) { + builtInMethods.add( new DateToXmlGregorianCalendar( typeFactory ) ); + builtInMethods.add( new XmlGregorianCalendarToDate( typeFactory ) ); + builtInMethods.add( new StringToXmlGregorianCalendar( typeFactory ) ); + builtInMethods.add( new XmlGregorianCalendarToString( typeFactory ) ); + builtInMethods.add( new CalendarToXmlGregorianCalendar( typeFactory ) ); + builtInMethods.add( new XmlGregorianCalendarToCalendar( typeFactory ) ); + } if ( isJaxbAvailable( typeFactory ) ) { builtInMethods.add( new JaxbElemToValue( typeFactory ) ); } + if ( isJava8TimeAvailable( typeFactory ) ) { builtInMethods.add( new ZonedDateTimeToCalendar( typeFactory ) ); builtInMethods.add( new CalendarToZonedDateTime( typeFactory ) ); - builtInMethods.add( new XmlGregorianCalendarToLocalDate( typeFactory ) ); - builtInMethods.add( new LocalDateToXmlGregorianCalendar( typeFactory ) ); + if ( isXmlGregorianCalendarPresent ) { + builtInMethods.add( new XmlGregorianCalendarToLocalDate( typeFactory ) ); + builtInMethods.add( new LocalDateToXmlGregorianCalendar( typeFactory ) ); + } } - if ( isJodaTimeAvailable( typeFactory ) ) { + if ( isJodaTimeAvailable( typeFactory ) && isXmlGregorianCalendarPresent ) { builtInMethods.add( new JodaDateTimeToXmlGregorianCalendar( typeFactory ) ); builtInMethods.add( new XmlGregorianCalendarToJodaDateTime( typeFactory ) ); builtInMethods.add( new JodaLocalDateTimeToXmlGregorianCalendar( typeFactory ) ); @@ -69,13 +75,18 @@ public class BuiltInMappingMethods { } private static boolean isJaxbAvailable(TypeFactory typeFactory) { - return typeFactory.isTypeAvailable( JaxbConstants.JAXB_ELEMENT_FQN ); + return JaxbConstants.isJaxbElementPresent() && typeFactory.isTypeAvailable( JaxbConstants.JAXB_ELEMENT_FQN ); } private static boolean isJava8TimeAvailable(TypeFactory typeFactory) { return typeFactory.isTypeAvailable( JavaTimeConstants.ZONED_DATE_TIME_FQN ); } + private static boolean isXmlGregorianCalendarAvailable(TypeFactory typeFactory) { + return XmlConstants.isXmlGregorianCalendarPresent() && + typeFactory.isTypeAvailable( XmlConstants.JAVAX_XML_DATATYPE_XMLGREGORIAN_CALENDAR ); + } + private static boolean isJodaTimeAvailable(TypeFactory typeFactory) { return typeFactory.isTypeAvailable( JodaTimeConstants.DATE_TIME_FQN ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/XmlElementDeclSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/XmlElementDeclSelector.java index 0eae22302..6051ad11e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/XmlElementDeclSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/XmlElementDeclSelector.java @@ -27,8 +27,6 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; -import javax.xml.bind.annotation.XmlElementDecl; -import javax.xml.bind.annotation.XmlElementRef; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; @@ -37,9 +35,11 @@ import org.mapstruct.ap.internal.prism.XmlElementDeclPrism; import org.mapstruct.ap.internal.prism.XmlElementRefPrism; /** - * Finds the {@link XmlElementRef} annotation on a field (of the mapping result type or its super types) matching the + * Finds the {@link javax.xml.bind.annotation.XmlElementRef} annotation on a field (of the mapping result type or its + * super types) matching the * target property name. Then selects those methods with matching {@code name} and {@code scope} attributes of the - * {@link XmlElementDecl} annotation, if that is present. Matching happens in the following order: + * {@link javax.xml.bind.annotation.XmlElementDecl} annotation, if that is present. Matching happens in the following + * order: *

    *
  1. Name and Scope matches
  2. *
  3. Scope matches
  4. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/ClassUtils.java b/processor/src/main/java/org/mapstruct/ap/internal/util/ClassUtils.java new file mode 100644 index 000000000..95d5bfbe9 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/ClassUtils.java @@ -0,0 +1,89 @@ +/** + * 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; + +/** + * Utilities for working with classes. It is mainly needed because using the {@link javax.lang.model.util.Elements} + * is not always correct. For example when compiling with JDK 9 and source version 8 classes from different modules + * are available by {@link javax.lang.model.util.Elements#getTypeElement(CharSequence)} but they are actually not + * if those modules are not added during compilation. + * + * @author Filip Hrisafov + */ +class ClassUtils { + + private ClassUtils() { + } + + /** + * Determine whether the {@link Class} identified by the supplied name is present + * and can be loaded. Will return {@code false} if either the class or + * one of its dependencies is not present or cannot be loaded. + * + * @param className the name of the class to check + * @param classLoader the class loader to use + * (may be {@code null}, which indicates the default class loader) + * + * @return whether the specified class is present + */ + static boolean isPresent(String className, ClassLoader classLoader) { + try { + ClassLoader classLoaderToUse = classLoader; + if ( classLoaderToUse == null ) { + classLoaderToUse = getDefaultClassLoader(); + } + classLoaderToUse.loadClass( className ); + return true; + } + catch ( ClassNotFoundException ex ) { + // Class or one of its dependencies is not present... + return false; + } + } + + /** + * Return the default ClassLoader to use: typically the thread context + * ClassLoader, if available; the ClassLoader that loaded the ClassUtils + * class will be used as fallback. + *

    Call this method if you intend to use the thread context ClassLoader + * in a scenario where you absolutely need a non-null ClassLoader reference: + * for example, for class path resource loading (but not necessarily for + * {@code Class.forName}, which accepts a {@code null} ClassLoader + * reference as well). + * + * @return the default ClassLoader (never {@code null}) + * + * @see Thread#getContextClassLoader() + */ + private static ClassLoader getDefaultClassLoader() { + ClassLoader cl = null; + try { + cl = Thread.currentThread().getContextClassLoader(); + } + catch ( Throwable ex ) { + // Cannot access thread context ClassLoader - falling back to system class loader... + } + if ( cl == null ) { + // No thread context class loader -> use class loader of this class. + cl = ClassUtils.class.getClassLoader(); + } + return cl; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/JaxbConstants.java b/processor/src/main/java/org/mapstruct/ap/internal/util/JaxbConstants.java index 7edf655a4..c7c9e6c55 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/JaxbConstants.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/JaxbConstants.java @@ -24,7 +24,18 @@ package org.mapstruct.ap.internal.util; public final class JaxbConstants { public static final String JAXB_ELEMENT_FQN = "javax.xml.bind.JAXBElement"; + private static final boolean IS_JAXB_ELEMENT_PRESENT = ClassUtils.isPresent( + JAXB_ELEMENT_FQN, + JaxbConstants.class.getClassLoader() + ); private JaxbConstants() { } + + /** + * @return {@code true} if {@link javax.xml.bind.JAXBElement} is present, {@code false} otherwise + */ + public static boolean isJaxbElementPresent() { + return IS_JAXB_ELEMENT_PRESENT; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/XmlConstants.java b/processor/src/main/java/org/mapstruct/ap/internal/util/XmlConstants.java new file mode 100644 index 000000000..5a341d881 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/XmlConstants.java @@ -0,0 +1,43 @@ +/** + * 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; + +/** + * Helper holding JAXB time full qualified class names for conversion registration + * + * @author Filip Hrisafov + */ +public final class XmlConstants { + + public static final String JAVAX_XML_DATATYPE_XMLGREGORIAN_CALENDAR = "javax.xml.datatype.XMLGregorianCalendar"; + private static final boolean IS_XML_GREGORIAN_CALENDAR_PRESENT = ClassUtils.isPresent( + JAVAX_XML_DATATYPE_XMLGREGORIAN_CALENDAR, + XmlConstants.class.getClassLoader() + ); + + private XmlConstants() { + } + + /** + * @return {@code true} if the {@link javax.xml.datatype.XMLGregorianCalendar} is present, {@code false} otherwise + */ + public static boolean isXmlGregorianCalendarPresent() { + return IS_XML_GREGORIAN_CALENDAR_PRESENT; + } +}