/* * Copyright (c) 2010 Mysema Ltd. * All rights reserved. * */ package com.mysema.rdfbean.object; import java.lang.annotation.Annotation; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; import com.mysema.commons.lang.Assert; import com.mysema.rdfbean.annotations.Mixin; import com.mysema.rdfbean.model.RDF; import com.mysema.rdfbean.model.UID; /** * @author sasa * */ public final class MappedClass { private static final Comparator<MappedPath> mappedPathComparator = new Comparator<MappedPath>() { @Override public int compare(MappedPath o1, MappedPath o2) { return o1.getOrder() - o2.getOrder(); } }; private final Class<?> clazz; @Nullable private MappedConstructor constructor; private final Set<MappedProperty<?>> dynamicProperties = new LinkedHashSet<MappedProperty<?>>(); private final Set<MappedProperty<?>> mixinProperties = new LinkedHashSet<MappedProperty<?>>(); @Nullable private MappedProperty<?> idProperty; private final Set<UID> mappedPredicates = new HashSet<UID>(); private final Set<UID> invMappedPredicates = new HashSet<UID>(); private Map<String, MappedPath> properties = new LinkedHashMap<String, MappedPath>(); private final List<MappedClass> mappedSuperClasses; @Nullable private final UID uid; @Nullable private final UID context; MappedClass(Class<?> clazz, @Nullable UID uid, @Nullable UID context, List<MappedClass> mappedSuperClasses) { this.clazz = Assert.notNull(clazz, "clazz"); this.uid = uid; this.context = context; this.mappedSuperClasses = mappedSuperClasses; mappedPredicates.add(RDF.type); } void addDynamicProperty(MappedProperty<?> property) { // FIXME How to handle mapped keys from superclass? dynamicProperties.add(property); } @SuppressWarnings("unchecked") void addMappedPath(MappedPath path) { if (path.getPredicatePath().size() > 0) { if (!path.get(0).inv()) { mappedPredicates.add(path.get(0).getUID()); } else { invMappedPredicates.add(path.get(0).getUID()); } } else if (path.getMappedProperty().isAnnotationPresent(Mixin.class)) { mixinProperties.add(path.getMappedProperty()); } MappedProperty property = path.getMappedProperty(); MappedPath existingPath = properties.get(property.getName()); if (path.getMappedProperty().isIdReference()) { if (idProperty != null) { throw new IllegalArgumentException("Duplicate ID property: " + idProperty + " and " + path.getMappedProperty()); } idProperty = path.getMappedProperty(); properties.put(path.getName(), path); } else if (existingPath != null) { MappedProperty existingProperty = existingPath.getMappedProperty(); if (property instanceof FieldProperty) { if (existingProperty instanceof FieldProperty) { throw new IllegalArgumentException("Cannot merge field properties: " + path + " into " + existingPath); } else { // Field property overrides method and constructor // properties properties.put(path.getName(), path); path.merge(existingPath); } } else { existingPath.merge(path); } } else { properties.put(path.getName(), path); } } void close() { MappedPath[] paths = properties.values().toArray(new MappedPath[properties.size()]); // Sort properties into bind order Arrays.sort(paths, mappedPathComparator); // Rebuild properties map using bind ordering properties = new LinkedHashMap<String, MappedPath>(); for (MappedPath path : paths) { properties.put(path.getName(), path); } // Close properties map from further changes properties = Collections.unmodifiableMap(properties); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } else if (obj instanceof MappedClass) { return clazz.equals(((MappedClass) obj).clazz); } else { return false; } } @Nullable public <T extends Annotation> T getAnnotation(Class<T> atype) { return clazz.getAnnotation(atype); } public String getClassNs() { return uid != null ? uid.ns() : ""; } @Nullable public MappedConstructor getConstructor() { return constructor; } public Collection<MappedProperty<?>> getDynamicProperties() { return dynamicProperties; } public Collection<MappedProperty<?>> getMixinProperties() { return mixinProperties; } @Nullable public MappedProperty<?> getIdProperty() { return idProperty; } public Class<?> getJavaClass() { return clazz; } public boolean hasProperty(String name) { return properties.containsKey(name); } public MappedPath getMappedPath(String name) { MappedPath path = properties.get(name); if (path != null) { return path; } else { throw new IllegalArgumentException("No such property: " + name + " in " + clazz); } } public List<MappedClass> getMappedSuperClasses() { return mappedSuperClasses; } public Collection<MappedPath> getProperties() { return properties.values(); } @Nullable public UID getUID() { return uid; } @Nullable public UID getContext() { return context; } @Override public int hashCode() { return clazz.hashCode(); } public boolean isEnum() { return clazz.isEnum(); } public Collection<UID> getMappedPredicates() { return mappedPredicates; } public Collection<UID> getInvMappedPredicates() { return invMappedPredicates; } public boolean isMappedPredicate(UID predicate) { return mappedPredicates.contains(predicate); } Type resolveTypeVariable(String typeVariableName, MappedClass declaringClass) { int i = 0; for (TypeVariable<?> typeParameter : declaringClass.clazz.getTypeParameters()) { if (typeParameter.getName().equals(typeVariableName)) { break; } else { i++; } } int j = 0; boolean found = false; for (MappedClass superClass : getMappedSuperClasses()) { if (declaringClass.equals(superClass)) { found = true; break; } else { j++; } } if (!found) { throw new SessionException("Super class declaration for " + declaringClass + " not found from " + this); } Type type = (j == 0 ? clazz.getGenericSuperclass() : clazz.getGenericInterfaces()[j - 1]); if (type instanceof ParameterizedType) { return ((ParameterizedType) type).getActualTypeArguments()[i]; } else { throw new SessionException("Generic parameters not supplied from " + this + " to " + declaringClass); } } void setMappedConstructor(@Nullable MappedConstructor constructor) { if (constructor == null && !clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers())) { throw new IllegalArgumentException("Default or mapped constructor required for " + clazz); } else { this.constructor = constructor; } } @Override public String toString() { return clazz.toString(); } }