package sk.nociar.jpacloner; import static java.util.Arrays.asList; import static java.util.Collections.unmodifiableList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import sk.nociar.jpacloner.graphs.EntityExplorer; import sk.nociar.jpacloner.graphs.GraphExplorer; /** * Generic explorer of JPA entities. Explored entities can be accessed by the method {@link #getEntities(Class)}. * * @author Miroslav Nociar * */ public final class JpaExplorer implements EntityExplorer { final PropertyFilter propertyFilter; final Map<Object, Set<String>> entities = new HashMap<Object, Set<String>>(); private JpaExplorer(PropertyFilter propertyFilter) { this.propertyFilter = propertyFilter; } private static final List<String> mapEntryProperties = unmodifiableList(asList("key", "value")); @Override public Collection<String> getProperties(Object object) { if (object == null) { return null; } if (object instanceof Entry) { return mapEntryProperties; } JpaClassInfo info = JpaClassInfo.get(object.getClass()); return info == null ? Collections.<String>emptyList() : info.getRelations(); } @Override @SuppressWarnings({ "rawtypes" }) public final Collection<?> explore(Object entity, String property) { if (entity == null || property == null) { return null; } if (entity instanceof Entry) { Entry entry = (Entry) entity; // handle Map.Entry#getKey() and Map.Entry#getValue() if ("key".equals(property)) { return Collections.singleton(entry.getKey()); } else if ("value".equals(property)) { return Collections.singleton(entry.getValue()); } else { throw new IllegalArgumentException("Map.Entry does not have property: " + property); } } if (!propertyFilter.test(entity, property)) { return null; } JpaClassInfo classInfo = JpaClassInfo.get(entity.getClass()); if (classInfo == null) { return null; } JpaPropertyInfo propertyInfo = classInfo.getPropertyInfo(property); if (propertyInfo == null || propertyInfo.isBasic()) { // explored property must be a relation return null; } addJpaObject(entity, property); final Object value = propertyInfo.getValue(entity); if (value == null) { return null; } final List<String> mappedBy = propertyInfo.getMappedBy(); Collection<?> exploredObjects = null; if (value instanceof Collection) { // Collection property exploredObjects = (Collection) value; for (Object object : exploredObjects) { addJpaObject(object); } // handle mappedBy handleMappedBy(exploredObjects, mappedBy); } else if (value instanceof Map) { // Map property Map map = (Map) value; exploredObjects = map.entrySet(); for (Object e : exploredObjects) { Entry entry = (Entry) e; addJpaObject(entry.getKey()); addJpaObject(entry.getValue()); } // handle mappedBy handleMappedBy(map.values(), mappedBy); } else { // singular property addJpaObject(value); exploredObjects = Collections.singleton(value); // handle mappedBy handleMappedBy(exploredObjects, mappedBy); } return exploredObjects; } private void handleMappedBy(Collection<?> objects, List<String> mappedBy) { if (mappedBy == null || mappedBy.isEmpty()) { return; } for (Object o : objects) { handleMappedBy(o, mappedBy, 0); } } private void handleMappedBy(Object o, List<String> mappedBy, int idx) { Collection<?> explored = explore(o, mappedBy.get(idx)); idx++; if (explored != null && idx < mappedBy.size()) { for (Object e : explored) { handleMappedBy(e, mappedBy, idx); } } } private void addJpaObject(Object object) { if (object != null && JpaClassInfo.getJpaClass(object.getClass()) != null) { if (!entities.containsKey(object)) { entities.put(object, new HashSet<String>()); } } } private void addJpaObject(Object object, String property) { if (object != null && JpaClassInfo.getJpaClass(object.getClass()) != null) { Set<String> properties = entities.get(object); if (properties == null) { properties = new HashSet<String>(); entities.put(object, properties); } properties.add(property); } } /** * Returns all explored entities of the given class. * * @param clazz the entity class * @return a set of explored entities of the given class */ public <T> Set<T> getEntities(Class<T> clazz) { Set<T> set = new HashSet<T>(); for (Object entity : entities.keySet()) { if (clazz.isInstance(entity)) { set.add(clazz.cast(entity)); } } return set; } /** * Explores the passed JPA entity. The explored relations are specified by string patters. * For description of patterns see the {@link GraphExplorer}. */ public static JpaExplorer doExplore(Object root, String... patterns) { return doExplore(root, PropertyFilters.getDefaultFilter(), patterns); } /** * Explores the passed JPA entity. The explored relations are specified by string patters. * For description of patterns see the {@link GraphExplorer}. */ public static JpaExplorer doExplore(Object root, PropertyFilter propertyFilter, String... patterns) { return doExplore(Collections.singleton(root), propertyFilter, patterns); } /** * Explores a collection of JPA entities. The explored relations are specified by string patters. * For description of patterns see the {@link GraphExplorer}. */ public static JpaExplorer doExplore(Collection<?> collection, String... patterns) { return doExplore(collection, PropertyFilters.getDefaultFilter(), patterns); } /** * Explores a collection of JPA entities. The explored relations are specified by string patters. * For description of patterns see the {@link GraphExplorer}. */ public static JpaExplorer doExplore(Collection<?> collection, PropertyFilter propertyFilter, String... patterns) { JpaExplorer jpaExplorer = new JpaExplorer(propertyFilter); for (Object root : collection) { jpaExplorer.addJpaObject(root); } if (patterns != null) { for (String pattern : patterns) { GraphExplorer graphExplorer = GraphExplorer.get(pattern); graphExplorer.explore(collection, jpaExplorer); } } return jpaExplorer; } }