/*******************************************************************************
* Copyright (c) 2011 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.jboss.tools.cdi.seam.solder.core;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMemberValuePair;
import org.eclipse.jdt.core.IType;
import org.eclipse.osgi.util.NLS;
import org.jboss.tools.cdi.core.CDIConstants;
import org.jboss.tools.cdi.core.CDICoreNature;
import org.jboss.tools.cdi.core.CDICorePlugin;
import org.jboss.tools.cdi.core.CDIUtil;
import org.jboss.tools.cdi.core.IBean;
import org.jboss.tools.cdi.core.ICDIProject;
import org.jboss.tools.cdi.core.IClassBean;
import org.jboss.tools.cdi.core.IProducer;
import org.jboss.tools.cdi.core.IProducerField;
import org.jboss.tools.cdi.core.IQualifierDeclaration;
import org.jboss.tools.cdi.core.IRootDefinitionContext;
import org.jboss.tools.cdi.core.IScope;
import org.jboss.tools.cdi.core.extension.ICDIExtension;
import org.jboss.tools.cdi.core.extension.feature.IAmbiguousBeanResolverFeature;
import org.jboss.tools.cdi.core.extension.feature.IBeanKeyProvider;
import org.jboss.tools.cdi.core.extension.feature.IBeanStoreFeature;
import org.jboss.tools.cdi.core.extension.feature.IProcessAnnotatedTypeFeature;
import org.jboss.tools.cdi.core.extension.feature.IValidatorFeature;
import org.jboss.tools.cdi.internal.core.impl.BeanMember;
import org.jboss.tools.cdi.internal.core.impl.CDIProject;
import org.jboss.tools.cdi.internal.core.impl.definition.AbstractMemberDefinition;
import org.jboss.tools.cdi.internal.core.impl.definition.BeanMemberDefinition;
import org.jboss.tools.cdi.internal.core.impl.definition.FieldDefinition;
import org.jboss.tools.cdi.internal.core.impl.definition.MethodDefinition;
import org.jboss.tools.cdi.internal.core.impl.definition.TypeDefinition;
import org.jboss.tools.cdi.internal.core.validation.CDICoreValidator;
import org.jboss.tools.cdi.seam.solder.core.validation.SeamSolderValidationMessages;
import org.jboss.tools.common.java.IAnnotationDeclaration;
import org.jboss.tools.common.java.IJavaAnnotation;
import org.jboss.tools.common.java.IJavaReference;
import org.jboss.tools.common.java.IParametedType;
import org.jboss.tools.common.java.ITypeDeclaration;
import org.jboss.tools.common.java.impl.AnnotationLiteral;
import org.jboss.tools.common.preferences.SeverityPreferences;
import org.jboss.tools.common.text.ITextSourceReference;
/**
* Implements support for org.jboss.seam.solder.bean.defaultbean.DefaultBeanExtension.
*
* In processing annotated type adds to each bean definition, which is a default bean,
* faked @Typed annotation with type set by @DefaultBean.
*
* In resolving ambiguous beans removes default beans out of the result set if it
* contains at least one non-default bean;
*
* @author Viacheslav Kabanovich
*
*/
public class CDISeamSolderDefaultBeanExtension implements ICDIExtension, IProcessAnnotatedTypeFeature, IAmbiguousBeanResolverFeature, IValidatorFeature, IBeanKeyProvider, IBeanStoreFeature {
private static String ID = "org.jboss.solder.bean.defaultbean.DefaultBeanExtension"; //$NON-NLS-1$
private static String ID_30 = "org.jboss.seam.solder.bean.defaultbean.DefaultBeanExtension"; //$NON-NLS-1$
protected Map<String, Set<IBean>> defaultBeansByKey = new HashMap<String, Set<IBean>>();
public static CDISeamSolderDefaultBeanExtension getExtension(CDICoreNature project) {
ICDIExtension result = project.getExtensionManager().getExtensionByRuntime(ID);
if(result == null) {
result = project.getExtensionManager().getExtensionByRuntime(ID_30);
}
if(result instanceof CDISeamSolderDefaultBeanExtension) {
return (CDISeamSolderDefaultBeanExtension)result;
}
return null;
}
protected Version getVersion() {
return Version.instance;
}
public void processAnnotatedType(TypeDefinition typeDefinition, IRootDefinitionContext context) {
String defaultBeanAnnotationTypeName = getVersion().getDefaultBeanAnnotationTypeName();
boolean defaultBean = typeDefinition.isAnnotationPresent(defaultBeanAnnotationTypeName);
IJavaAnnotation beanTyped = null;
if(defaultBean) {
beanTyped = createFakeTypedAnnotation(typeDefinition, context);
if(beanTyped != null) {
typeDefinition.addAnnotation(beanTyped, context);
}
}
List<MethodDefinition> ms = typeDefinition.getMethods();
for (MethodDefinition m: ms) {
if(m.isAnnotationPresent(CDIConstants.PRODUCES_ANNOTATION_TYPE_NAME)) {
if(defaultBean || m.isAnnotationPresent(defaultBeanAnnotationTypeName)) {
IJavaAnnotation methodTyped = createFakeTypedAnnotation(m, context);
if(methodTyped != null) {
m.addAnnotation(methodTyped, context);
}
}
}
}
List<FieldDefinition> fs = typeDefinition.getFields();
for (FieldDefinition f: fs) {
if(f.isAnnotationPresent(CDIConstants.PRODUCES_ANNOTATION_TYPE_NAME)) {
if(defaultBean || f.isAnnotationPresent(defaultBeanAnnotationTypeName)) {
IJavaAnnotation fieldTyped = createFakeTypedAnnotation(f, context);
if(fieldTyped != null) {
f.addAnnotation(fieldTyped, context);
}
}
}
}
}
IJavaAnnotation createFakeTypedAnnotation(AbstractMemberDefinition def, IRootDefinitionContext context) {
IJavaAnnotation result = null;
IAnnotationDeclaration a = def.getAnnotation(getVersion().getDefaultBeanAnnotationTypeName());
if(a != null) {
Object n = a.getMemberValue(null);
if(n != null && n.toString().length() > 0) {
String defaultType = n.toString();
IType typedAnnotation = context.getProject().getType(CDIConstants.TYPED_ANNOTATION_TYPE_NAME);
if (typedAnnotation != null) {
result = new AnnotationLiteral(def.getResource(), a.getStartPosition(), a.getLength(), defaultType, IMemberValuePair.K_CLASS, typedAnnotation);
}
}
} else if(def instanceof BeanMemberDefinition) {
ITypeDeclaration type = BeanMember.getTypeDeclaration(def, context.getProject().getTypeFactory());
if(type != null) {
IType typedAnnotation = context.getProject().getType(CDIConstants.TYPED_ANNOTATION_TYPE_NAME);
if (typedAnnotation != null) {
result = new AnnotationLiteral(def.getResource(), type.getStartPosition(), type.getLength(), type.getType().getFullyQualifiedName(), IMemberValuePair.K_CLASS, typedAnnotation);
}
}
}
return result;
}
public Collection<IBean> getResolvedBeans(Collection<IBean> result) {
Set<IBean> defaultBeans = new HashSet<IBean>();
for (IBean b: result) {
if(isBeanDefault(b)) {
defaultBeans.add(b);
}
}
if(!defaultBeans.isEmpty() && defaultBeans.size() < result.size()) {
result.removeAll(defaultBeans);
}
return result;
}
public boolean isBeanDefault(IBean bean) {
String defaultBeanAnnotationTypeName = getVersion().getDefaultBeanAnnotationTypeName();
if(bean.isAnnotationPresent(defaultBeanAnnotationTypeName)) {
return true;
} else if(bean instanceof IProducer) {
IProducer producer = (IProducer)bean;
IClassBean parent = producer.getClassBean();
if(parent != null && parent.isAnnotationPresent(defaultBeanAnnotationTypeName)) {
return true;
}
}
return false;
}
public void validateResource(IFile file, CDICoreValidator validator) {
String defaultBeanAnnotationTypeName = getVersion().getDefaultBeanAnnotationTypeName();
ICDIProject cdiProject = CDICorePlugin.getCDIProject(file.getProject(), true);
if(cdiProject == null) return;
for (IBean bean: cdiProject.getBeans(file.getFullPath())) {
if(isBeanDefault(bean)) {
ITextSourceReference a = bean.getAnnotation(defaultBeanAnnotationTypeName);
if(a == null) {
Collection<ITypeDeclaration> ds = bean.getAllTypeDeclarations();
if(!ds.isEmpty()) {
IMember e = bean instanceof IJavaReference ? ((IJavaReference)bean).getSourceMember() : bean.getBeanClass();
a = CDIUtil.convertToJavaSourceReference(ds.iterator().next(), e);
} else {
continue;
}
}
if(bean instanceof IProducerField) {
IClassBean cb = ((IProducerField) bean).getClassBean();
IScope scope = cb.getScope();
if(scope != null && scope.isNorlmalScope()) {
validator.addError(SeamSolderValidationMessages.DEFAULT_PRODUCER_FIELD_ON_NORMAL_SCOPED_BEAN,
CDISeamSolderPreferences.DEFAULT_PRODUCER_FIELD_ON_NORMAL_SCOPED_BEAN, new String[]{}, a, file);
}
}
IQualifierDeclaration[] qs = bean.getQualifierDeclarations().toArray(new IQualifierDeclaration[0]);
IParametedType type = getDefaultType(bean);
if(type != null) {
String key = createKey(type, bean.getQualifierDeclarations(true));
Set<IBean> linked = defaultBeansByKey.get(key);
if(linked != null) {
for (IBean link: linked) {
if(link.getSourcePath() != null) {
validator.getValidationContext().addLinkedCoreResource(CDICoreValidator.SHORT_ID, key, link.getSourcePath(), true);
}
}
}
StringBuilder otherDefaultBeans = new StringBuilder();
for (IBean b: cdiProject.getBeans(false, type, qs)) {
try {
if(b != bean && isBeanDefault(b)
&& CDIProject.areMatchingQualifiers(bean.getQualifierDeclarations(), b.getQualifierDeclarations(true))) {
if(otherDefaultBeans.length() > 0) {
otherDefaultBeans.append(", ");
}
otherDefaultBeans.append(b.getElementName());
}
} catch (CoreException e) {
CDISeamSolderCorePlugin.getDefault().logError(e);
}
}
if(otherDefaultBeans.length() > 0) {
String message = NLS.bind(SeamSolderValidationMessages.IDENTICAL_DEFAULT_BEANS, otherDefaultBeans);
validator.addError(message, CDISeamSolderPreferences.IDENTICAL_DEFAULT_BEANS, new String[]{}, a, file);
}
}
}
}
}
public SeverityPreferences getSeverityPreferences() {
return CDISeamSolderPreferences.getInstance();
}
private IParametedType getDefaultType(IBean bean) {
Collection<IParametedType> ts = bean.getLegalTypes();
if(ts.size() < 3) {
for (IParametedType t: ts) {
if(!"java.lang.Object".equals(t.getType().getFullyQualifiedName())) {
return t;
}
}
}
return null;
}
@Override
public String getKey(IBean bean) {
if(isBeanDefault(bean)) {
IParametedType type = getDefaultType(bean);
if(type != null) {
return createKey(type, bean.getQualifierDeclarations(true));
}
}
return null;
}
private String createKey(IParametedType type, Collection<IQualifierDeclaration> qs) {
Set<String> ss = new TreeSet<String>();
for (IQualifierDeclaration q: qs) {
if(!q.getTypeName().equals(CDIConstants.ANY_QUALIFIER_TYPE_NAME)
&& !q.getTypeName().equals(CDIConstants.DEFAULT_QUALIFIER_TYPE_NAME)) {
ss.add(q.getTypeName());
}
}
StringBuilder sb = new StringBuilder();
sb.append("#DefaultBean_").append(type.getType().getFullyQualifiedName());
for (String s: ss) {
sb.append(':').append(s);
}
return sb.toString();
}
public synchronized void updateCaches(ICDIProject project) {
Map<String, Set<IBean>> map = new HashMap<String, Set<IBean>>();
IBean[] beans = project.getBeans();
for (IBean b: beans) {
String key = getKey(b);
if(key != null) {
Set<IBean> bs = map.get(key);
if(bs == null) {
bs = new HashSet<IBean>();
map.put(key, bs);
}
bs.add(b);
}
}
defaultBeansByKey = map;
}
}