/*
* Copyright (C) 2014 The Datarobot Authors
*
* 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.arconsis.android.datarobot.builder.schema.reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.processing.Messager;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.tools.Diagnostic.Kind;
import com.arconsis.android.datarobot.builder.schema.data.Association;
import com.arconsis.android.datarobot.builder.schema.data.Column;
import com.arconsis.android.datarobot.builder.schema.data.Table;
import com.arconsis.android.datarobot.builder.schema.visitor.AssociationElementResolvingTypeVisitor;
import com.arconsis.android.datarobot.builder.schema.visitor.ColumnElementResolvingTypeVisitor;
import com.arconsis.android.datarobot.builder.schema.visitor.EmptyContructorVisitor;
import com.arconsis.android.datarobot.entity.PrimaryKey;
import com.arconsis.android.datarobot.entity.Relationship;
import com.arconsis.android.datarobot.schema.AssociationType;
/**
* @author Alexander Frank
* @author Falk Appel
*/
public class TableReader implements Reader<Table> {
private final TypeElement entity;
private final Set<String> entityNames;
private final Messager messager;
public TableReader(final TypeElement entity, final Set<String> entityNames, final Messager messager) {
this.entity = entity;
this.entityNames = entityNames;
this.messager = messager;
}
@Override
public Table read() {
int primaryKeyCount = 0;
boolean noArgsConstructor = false;
Table table = new Table(entity.getSimpleName().toString(), entity.toString());
Map<String, AtomicInteger> countedToManyAssociations = new HashMap<String, AtomicInteger>();
for (Element child : entity.getEnclosedElements()) {
if (child.accept(new EmptyContructorVisitor(), null) != null) {
noArgsConstructor = true;
}
VariableElement column = child.accept(new ColumnElementResolvingTypeVisitor(), null);
if (column != null) {
if (hasAmbiguosAssociationDeclaration(column)) {
return null;
}
if (column.getAnnotation(PrimaryKey.class) != null && ++primaryKeyCount > 1) {
messager.printMessage(Kind.ERROR, "Only one @PrimaryKey is allowed within an @Entity", entity);
return null;
}
ColumnReader columnReader = new ColumnReader(column, messager);
Column read = columnReader.read();
if (read != null) {
table.addColumn(read);
}
}
VariableElement association = child.accept(new AssociationElementResolvingTypeVisitor(), null);
if (association != null) {
AssociationReader associationReader = new AssociationReader(association, entityNames, messager);
Association read = associationReader.read();
if (read != null) {
table.addAssociation(read);
if (AssociationType.TO_MANY == read.getCardinality()) {
if (countedToManyAssociations.containsKey(read.getCanonicalTypeInEntity())) {
countedToManyAssociations.get(read.getCanonicalTypeInEntity()).incrementAndGet();
} else {
countedToManyAssociations.put(read.getCanonicalTypeInEntity(), new AtomicInteger(1));
}
}
}
}
}
if (primaryKeyCount == 0) {
messager.printMessage(Kind.ERROR, "No @PrimaryKey was set for this @Entity", entity);
return null;
}
if (!noArgsConstructor) {
messager.printMessage(Kind.ERROR, "An @Entity needs to have a no-args constructor", entity);
return null;
}
List<String> multipeToManyAssocitionsInEntity = getMultipeToManyAssociations(countedToManyAssociations);
if (multipeToManyAssocitionsInEntity.size() > 0) {
messager.printMessage(Kind.ERROR,
"You can not have multipe To-Many associations of the same type within one @Entity at this moment. Conflicts are with Top-Many associtions of type "
+ join(multipeToManyAssocitionsInEntity, ", "), entity);
}
return table;
}
private boolean hasAmbiguosAssociationDeclaration(final VariableElement variableElement) {
if (variableElement.getAnnotation(Relationship.class) != null) {
messager.printMessage(Kind.ERROR, "Only @Column or @Relationship is allowed, but not both", variableElement);
return true;
}
return false;
}
private List<String> getMultipeToManyAssociations(final Map<String, AtomicInteger> countedToManyAssociations) {
List<String> multipe = new ArrayList<String>();
for (Map.Entry<String, AtomicInteger> entry : countedToManyAssociations.entrySet()) {
if (entry.getValue().get() > 1) {
multipe.add(entry.getKey());
}
}
return multipe;
}
private String join(final Collection<String> stringCollection, final String separator) {
StringBuilder builder = new StringBuilder();
int count = 0;
int size = stringCollection.size();
for (String entry : stringCollection) {
builder.append(entry);
if (count++ < size - 1) {
builder.append(separator);
}
}
return builder.toString();
}
}