/* * * Copyright (C) 2015 Noorq, Inc. * 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 com.noorq.casser.mapping; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.BitSet; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.noorq.casser.config.CasserSettings; import com.noorq.casser.core.Casser; import com.noorq.casser.mapping.annotation.Table; import com.noorq.casser.mapping.annotation.Tuple; import com.noorq.casser.mapping.annotation.UDT; import com.noorq.casser.support.CasserMappingException; public final class CasserMappingEntity implements CasserEntity { private final Class<?> iface; private final CasserEntityType type; private final IdentityName name; private final ImmutableMap<String, CasserProperty> props; private final ImmutableList<CasserProperty> orderedProps; public CasserMappingEntity(Class<?> iface) { this(iface, autoDetectType(iface)); } public CasserMappingEntity(Class<?> iface, CasserEntityType type) { if (iface == null || !iface.isInterface()) { throw new IllegalArgumentException("invalid parameter " + iface); } this.iface = iface; this.type = Objects.requireNonNull(type, "type is empty"); this.name = resolveName(iface, type); CasserSettings settings = Casser.settings(); Method[] all = iface.getDeclaredMethods(); List<CasserProperty> propsLocal = new ArrayList<CasserProperty>(); ImmutableMap.Builder<String, CasserProperty> propsBuilder = ImmutableMap.builder(); for (Method m : all) { if (settings.getGetterMethodDetector().apply(m)) { CasserProperty prop = new CasserMappingProperty(this, m); propsBuilder.put(prop.getPropertyName(), prop); propsLocal.add(prop); } } this.props = propsBuilder.build(); Collections.sort(propsLocal, TypeAndOrdinalColumnComparator.INSTANCE); this.orderedProps = ImmutableList.copyOf(propsLocal); validateOrdinals(); } @Override public CasserEntityType getType() { return type; } @Override public Class<?> getMappingInterface() { return iface; } @Override public Collection<CasserProperty> getOrderedProperties() { return orderedProps; } @Override public CasserProperty getProperty(String name) { return props.get(name); } @Override public IdentityName getName() { return name; } private static IdentityName resolveName(Class<?> iface, CasserEntityType type) { switch(type) { case TABLE: return MappingUtil.getTableName(iface, true); case TUPLE: return IdentityName.of(MappingUtil.getDefaultEntityName(iface), false); case UDT: return MappingUtil.getUserDefinedTypeName(iface, true); } throw new CasserMappingException("invalid entity type " + type + " in " + type); } private static CasserEntityType autoDetectType(Class<?> iface) { Objects.requireNonNull(iface, "empty iface"); if (null != iface.getDeclaredAnnotation(Table.class)) { return CasserEntityType.TABLE; } else if (null != iface.getDeclaredAnnotation(Tuple.class)) { return CasserEntityType.TUPLE; } else if (null != iface.getDeclaredAnnotation(UDT.class)) { return CasserEntityType.UDT; } throw new CasserMappingException("entity must be annotated by @Table or @Tuple or @UserDefinedType " + iface); } private void validateOrdinals() { switch(getType()) { case TABLE: validateOrdinalsForTable(); break; case TUPLE: validateOrdinalsInTuple(); break; default: break; } } private void validateOrdinalsForTable() { BitSet partitionKeys = new BitSet(); BitSet clusteringColumns = new BitSet(); for (CasserProperty prop : getOrderedProperties()) { ColumnType type = prop.getColumnType(); int ordinal = prop.getOrdinal(); switch(type) { case PARTITION_KEY: if (partitionKeys.get(ordinal)) { throw new CasserMappingException("detected two or more partition key columns with the same ordinal " + ordinal + " in " + prop.getEntity()); } partitionKeys.set(ordinal); break; case CLUSTERING_COLUMN: if (clusteringColumns.get(ordinal)) { throw new CasserMappingException("detected two or clustering columns with the same ordinal " + ordinal + " in " + prop.getEntity()); } clusteringColumns.set(ordinal); break; default: break; } } } private void validateOrdinalsInTuple() { boolean[] ordinals = new boolean[props.size()]; getOrderedProperties().forEach(p -> { int ordinal = p.getOrdinal(); if (ordinal < 0 || ordinal >= ordinals.length) { throw new CasserMappingException("invalid ordinal " + ordinal + " found for property " + p.getPropertyName() + " in " + p.getEntity()); } if (ordinals[ordinal]) { throw new CasserMappingException("detected two or more properties with the same ordinal " + ordinal + " in " + p.getEntity()); } ordinals[ordinal] = true; }); for (int i = 0; i != ordinals.length; ++i) { if (!ordinals[i]) { throw new CasserMappingException("detected absent ordinal " + i + " in " + this); } } } @Override public String toString() { StringBuilder str = new StringBuilder(); str.append(iface.getSimpleName()) .append("(").append(name.getName()).append(") ") .append(type.name().toLowerCase()) .append(":\n"); for (CasserProperty prop : getOrderedProperties()) { str.append(prop.toString()); str.append("\n"); } return str.toString(); } }