/*
* JBoss, Home of Professional Open Source
* Copyright 2011, Red Hat, Inc. and/or its affiliates, and individual
* contributors by the @authors tag. See the copyright.txt in the
* distribution for a full listing of individual contributors.
*
* 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.jboss.solder.config.xml.model;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.enterprise.inject.spi.BeanManager;
import org.jboss.solder.config.xml.core.BeanResult;
import org.jboss.solder.config.xml.core.BeanResultType;
import org.jboss.solder.config.xml.core.VirtualProducerField;
import org.jboss.solder.config.xml.fieldset.FieldValueObject;
import org.jboss.solder.config.xml.util.TypeOccuranceInformation;
import org.jboss.solder.config.xml.util.XmlConfigurationException;
import org.jboss.solder.literal.InjectLiteral;
import org.jboss.solder.properties.Properties;
import org.jboss.solder.properties.Property;
import org.jboss.solder.properties.query.NamedPropertyCriteria;
import org.jboss.solder.properties.query.PropertyQueries;
import org.jboss.solder.properties.query.PropertyQuery;
import org.jboss.solder.reflection.Reflections;
public class ClassXmlItem extends AbstractXmlItem {
private HashSet<TypeOccuranceInformation> allowed = new HashSet<TypeOccuranceInformation>();
public ClassXmlItem(XmlItem parent, Class<?> c, Map<String, String> attributes, String document, int lineno) {
super(XmlItemType.CLASS, parent, c, null, attributes, document, lineno);
allowed.add(TypeOccuranceInformation.of(XmlItemType.ANNOTATION, null, null));
allowed.add(TypeOccuranceInformation.of(XmlItemType.FIELD, null, null));
allowed.add(TypeOccuranceInformation.of(XmlItemType.METHOD, null, null));
allowed.add(TypeOccuranceInformation.of(XmlItemType.PARAMETERS, null, null));
allowed.add(TypeOccuranceInformation.of(XmlItemType.REPLACE, null, null));
allowed.add(TypeOccuranceInformation.of(XmlItemType.MODIFIES, null, null));
allowed.add(TypeOccuranceInformation.of(XmlItemType.VALUE, null, null));
allowed.add(TypeOccuranceInformation.of(XmlItemType.ENTRY, null, null));
}
public Set<TypeOccuranceInformation> getAllowedItem() {
return allowed;
}
public Set<PropertyXmlItem> getShorthandFieldValues() {
Set<PropertyXmlItem> values = new HashSet<PropertyXmlItem>();
for (Entry<String, String> e : attributes.entrySet()) {
PropertyQuery<Object> query = PropertyQueries.createQuery(getJavaClass());
query.addCriteria(new NamedPropertyCriteria(e.getKey()));
Property<Object> property = query.getFirstWritableResult();
if (property != null) {
property.setAccessible();
values.add(new PropertyXmlItem(this, property, e.getValue(), null, document, lineno));
} else {
throw new XmlConfigurationException("Could not resolve field: " + e.getKey(), document, lineno);
}
}
return values;
}
public BeanResult<?> createBeanResult(BeanManager manager) {
boolean override = !getChildrenOfType(ReplacesXmlItem.class).isEmpty();
boolean extend = !getChildrenOfType(ModifiesXmlItem.class).isEmpty();
if (override && extend) {
throw new XmlConfigurationException("A bean may not both <override> and <extend> an existing bean", getDocument(), getLineno());
}
BeanResultType beanType = override ? BeanResultType.REPLACES : (extend ? BeanResultType.MODIFIES : BeanResultType.ADD);
List<BeanResult<?>> inlineBeans = new ArrayList<BeanResult<?>>();
// get all the field values from the bean
Set<String> configuredFields = new HashSet<String>();
List<FieldValueObject> fields = new ArrayList<FieldValueObject>();
for (PropertyXmlItem xi : getChildrenOfType(PropertyXmlItem.class)) {
inlineBeans.addAll(xi.getInlineBeans());
FieldValueObject f = xi.getFieldValue();
if (f != null) {
fields.add(f);
configuredFields.add(xi.getFieldName());
}
}
for (PropertyXmlItem f : getShorthandFieldValues()) {
if (configuredFields.contains(f.getFieldName())) {
throw new XmlConfigurationException("Field configured in two places: " + getJavaClass().getName() + "." + f.getFieldName(), getDocument(), getLineno());
}
fields.add(f.getFieldValue());
}
// if it is an extend we want to read the annotations from the underlying
// class
BeanResult<?> result = new BeanResult(getJavaClass(), extend, beanType, fields, inlineBeans, manager);
List<ParameterXmlItem> constList = new ArrayList<ParameterXmlItem>();
for (AnnotationXmlItem item : getChildrenOfType(AnnotationXmlItem.class)) {
Annotation a = AnnotationUtils.createAnnotation(item);
result.addToClass(a);
}
// list of constructor arguments
List<ParametersXmlItem> constructorParameters = getChildrenOfType(ParametersXmlItem.class);
if (constructorParameters.size() > 1) {
throw new XmlConfigurationException("A method may only have a single <parameters> element", getDocument(), getLineno());
} else if (!constructorParameters.isEmpty()) {
for (ParameterXmlItem item : constructorParameters.get(0).getChildrenOfType(ParameterXmlItem.class)) {
constList.add(item);
}
}
for (PropertyXmlItem item : getChildrenOfType(PropertyXmlItem.class)) {
if (item.getField() != null) {
for (AnnotationXmlItem fi : item.getChildrenOfType(AnnotationXmlItem.class)) {
Annotation a = AnnotationUtils.createAnnotation(fi);
result.addToField(item.getField(), a);
}
} else if (!item.getChildrenOfType(AnnotationXmlItem.class).isEmpty()) {
throw new XmlConfigurationException("Property's that do not have an underlying field may not have annotations added to them", item.getDocument(), item.getLineno());
}
}
for (MethodXmlItem item : getChildrenOfType(MethodXmlItem.class)) {
int paramCount = 0;
for (AnnotationXmlItem fi : item.getChildrenOfType(AnnotationXmlItem.class)) {
Annotation a = AnnotationUtils.createAnnotation(fi);
result.addToMethod(item.getMethod(), a);
}
List<ParametersXmlItem> parameters = item.getChildrenOfType(ParametersXmlItem.class);
if (parameters.size() > 1) {
throw new XmlConfigurationException("A method may only have a single <parameters> element", item.getDocument(), item.getLineno());
} else if (!parameters.isEmpty()) {
for (ParameterXmlItem fi : parameters.get(0).getChildrenOfType(ParameterXmlItem.class)) {
int param = paramCount++;
for (AnnotationXmlItem pan : fi.getChildrenOfType(AnnotationXmlItem.class)) {
Annotation a = AnnotationUtils.createAnnotation(pan);
result.addToMethodParameter(item.getMethod(), param, a);
}
}
}
}
if (!constList.isEmpty()) {
int paramCount = 0;
Constructor<?> constructor = resolveConstructor(constList);
// we automatically add inject to the constructor
result.addToConstructor(constructor, InjectLiteral.INSTANCE);
for (ParameterXmlItem fi : constList) {
int param = paramCount++;
for (AnnotationXmlItem pan : fi.getChildrenOfType(AnnotationXmlItem.class)) {
Annotation a = AnnotationUtils.createAnnotation(pan);
result.addToConstructorParameter(constructor, param, a);
}
}
}
return result;
}
/**
* Builds up a bean result for a virtual producer field.
*
* @param manager
* @return
*/
public BeanResult<?> createVirtualFieldBeanResult(BeanManager manager) {
boolean override = !getChildrenOfType(ReplacesXmlItem.class).isEmpty();
boolean extend = !getChildrenOfType(ModifiesXmlItem.class).isEmpty();
if (override || extend) {
throw new XmlConfigurationException("A virtual producer field may not containe <override> or <extend> tags", getDocument(), getLineno());
}
Field member = org.jboss.solder.config.xml.util.Reflections.getField(VirtualProducerField.class, "field");
member.setAccessible(true);
ClassXmlItem vclass = new ClassXmlItem(null, VirtualProducerField.class, Collections.<String, String>emptyMap(), document, lineno);
PropertyXmlItem field = new PropertyXmlItem(vclass, Properties.createProperty(member), null, getJavaClass(), document, lineno);
vclass.addChild(field);
for (XmlItem i : this.getChildren()) {
field.addChild(i);
}
field.resolveChildren(manager);
BeanResult<?> result = vclass.createBeanResult(manager);
result.overrideFieldType(member, this.getJavaClass());
return result;
}
private Constructor<?> resolveConstructor(List<ParameterXmlItem> constList) {
Class<?>[] params = new Class[constList.size()];
for (int i = 0; i < constList.size(); ++i) {
params[i] = constList.get(i).getJavaClass();
}
Constructor<?> ret = Reflections.findDeclaredConstructor(getJavaClass(), params);
if (ret == null) {
throw new XmlConfigurationException("Could not resolve constructor for " + getJavaClass() + " with arguments " + params, getDocument(), getLineno());
}
return ret;
}
}