/* * 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.jdbi.v3.jpa; import static java.util.Collections.synchronizedMap; import static java.util.Collections.unmodifiableList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.persistence.Column; import javax.persistence.MappedSuperclass; import java.beans.IndexedPropertyDescriptor; import java.beans.IntrospectionException; import java.beans.Introspector; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.WeakHashMap; import java.util.stream.Stream; class JpaClass<C> { private static final Map<Class<?>, JpaClass<?>> cache = synchronizedMap(new WeakHashMap<>()); @SuppressWarnings("unchecked") public static <C> JpaClass<C> get(Class<C> clazz) { return (JpaClass<C>) cache.computeIfAbsent(clazz, JpaClass::new); } private final List<JpaMember> members; private JpaClass(Class<C> clazz) { this.members = unmodifiableList(new ArrayList<>(inspectClass(clazz))); logger.debug("init {}: {} members.", clazz, members.size()); } private static Collection<JpaMember> inspectClass(Class<?> clazz) { Map<String, JpaMember> members = new HashMap<>(); inspectFields(clazz, members); inspectAnnotatedProperties(clazz, members); inspectSuperclasses(clazz, members); inspectNonAnnotatedProperties(clazz, members); return members.values(); } private static void inspectSuperclasses(Class<?> clazz, Map<String, JpaMember> members) { while ((clazz = clazz.getSuperclass()) != null) { if (clazz.isAnnotationPresent(MappedSuperclass.class)) { inspectFields(clazz, members); } } } private static void inspectFields(Class<?> clazz, Map<String, JpaMember> members) { for (Field member : clazz.getDeclaredFields()) { if (members.containsKey(member.getName())) { continue; } Column column = member.getAnnotation(Column.class); if (column != null) { members.put(member.getName(), new JpaMember(clazz, column, member)); } } } private static void inspectAnnotatedProperties(Class<?> clazz, Map<String, JpaMember> members) { inspectProperties(clazz, members, true); } private static void inspectNonAnnotatedProperties(Class<?> clazz, Map<String, JpaMember> members) { inspectProperties(clazz, members, false); } private static void inspectProperties(Class<?> clazz, Map<String, JpaMember> members, boolean hasColumnAnnotation) { try { Stream.of(Introspector.getBeanInfo(clazz).getPropertyDescriptors()) .filter(property -> !members.containsKey(property.getName())) .filter(property -> !(property instanceof IndexedPropertyDescriptor)) .filter(property -> !"class".equals(property.getName())) .forEach(property -> { Method getter = property.getReadMethod(); Method setter = property.getWriteMethod(); Column column = Stream.of(getter, setter) .filter(Objects::nonNull) .map(method -> method.getAnnotation(Column.class)) .filter(Objects::nonNull) .findFirst() .orElse(null); if ((column != null) == hasColumnAnnotation) { members.put(property.getName(), new JpaMember(clazz, column, property)); } }); } catch (IntrospectionException e) { logger.warn("Unable to introspect " + clazz, e); } } public JpaMember lookupMember(String columnLabel) { String column = columnLabel.toLowerCase(Locale.ROOT); return members.stream() .filter(member -> column.equals(member.getColumnName().toLowerCase(Locale.ROOT))) .findFirst() .orElse(null); } public List<JpaMember> members() { return members; } private static final Logger logger = LoggerFactory.getLogger(JpaClass.class); }