/*
* Copyright (C) 2007 the original author or 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 org.codehaus.plexus.metadata.gleaner;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Configuration;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.component.repository.ComponentDescriptor;
import org.codehaus.plexus.component.repository.ComponentRequirement;
import org.codehaus.plexus.component.repository.ComponentRequirementList;
import org.codehaus.plexus.configuration.PlexusConfiguration;
import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
import org.codehaus.plexus.metadata.ann.AnnClass;
import org.codehaus.plexus.metadata.ann.AnnField;
import org.codehaus.plexus.metadata.ann.AnnReader;
import org.codehaus.plexus.util.IOUtil;
/**
* A class component gleaner which inspects each type for <tt>org.codehaus.plexus.component.annotations.*</tt> annotations
* and when found translates them into a {@link ComponentDescriptor}.
*
* @version $Id$
*/
public class AnnotationComponentGleaner
extends ComponentGleanerSupport
implements ClassComponentGleaner
{
public ComponentDescriptor<?> glean(String className, ClassLoader cl) throws ComponentGleanerException
{
assert className != null;
assert cl != null;
AnnClass annClass = readClass(className.replace('.', '/'), cl);
// Skip abstract classes
if (Modifier.isAbstract(annClass.getAccess())) {
return null;
}
Component anno = annClass.getAnnotation(Component.class);
if (anno == null) {
return null;
}
ComponentDescriptor<?> component = new ComponentDescriptor<Object>();
component.setRole(anno.role().getName());
component.setRoleHint(filterEmptyAsNull(anno.hint()));
component.setImplementation(className);
component.setVersion(filterEmptyAsNull(anno.version()));
component.setComponentType(filterEmptyAsNull(anno.type()));
component.setInstantiationStrategy(filterEmptyAsNull(anno.instantiationStrategy()));
component.setLifecycleHandler(filterEmptyAsNull(anno.lifecycleHandler()));
component.setComponentProfile(filterEmptyAsNull(anno.profile()));
component.setComponentComposer(filterEmptyAsNull(anno.composer()));
component.setComponentConfigurator(filterEmptyAsNull(anno.configurator()));
component.setComponentFactory(filterEmptyAsNull(anno.factory()));
component.setDescription(filterEmptyAsNull(anno.description()));
component.setAlias(filterEmptyAsNull(anno.alias()));
component.setIsolatedRealm(anno.isolatedRealm());
for (AnnClass c : getClasses(annClass, cl)) {
for (AnnField field : c.getFields().values()) {
ComponentRequirement requirement = findRequirement(field, c, cl);
if (requirement != null) {
component.addRequirement(requirement);
}
PlexusConfiguration config = findConfiguration(field, c, cl);
if (config != null) {
addChildConfiguration(component, config);
}
}
//
// TODO: Inspect methods?
//
}
return component;
}
private AnnClass readClass(String className, ClassLoader cl) throws ComponentGleanerException
{
InputStream is = null;
try
{
// only read annotation from project classes (not jars)
Enumeration<URL> en = cl.getResources( className + ".class" );
while ( en.hasMoreElements() ) {
URL url = en.nextElement();
if( url.toString().startsWith( "file:" ) )
{
is = url.openStream();
return AnnReader.read( is, cl );
}
}
throw new ComponentGleanerException("Can't find class " + className);
}
catch (IOException ex)
{
throw new ComponentGleanerException("Can't read class " + className, ex);
}
finally
{
IOUtil.close(is);
}
}
private AnnClass readClass2(String className, ClassLoader cl) throws ComponentGleanerException
{
InputStream is = null;
try
{
is = cl.getResourceAsStream(className + ".class");
return AnnReader.read(is, cl);
}
catch (IOException ex)
{
throw new ComponentGleanerException("Can't read class " + className, ex);
}
finally
{
IOUtil.close(is);
}
}
/**
* Returns a list of all of the classes which the given type inherits from.
*/
private List<AnnClass> getClasses(AnnClass annClass, ClassLoader cl) throws ComponentGleanerException {
assert annClass != null;
List<AnnClass> classes = new ArrayList<AnnClass>();
while(annClass!=null) {
classes.add(annClass);
if(annClass.getSuperName()!=null) {
annClass = readClass2(annClass.getSuperName(), cl);
} else {
break;
}
//
// TODO: See if we need to include interfaces here too?
//
}
return classes;
}
private ComponentRequirement findRequirement(final AnnField field, AnnClass annClass, ClassLoader cl)
throws ComponentGleanerException
{
assert field != null;
Requirement anno = field.getAnnotation(Requirement.class);
if (anno == null) {
return null;
}
String fieldType = field.getType();
// TODO implement type resolution without loading classes
Class<?> type;
try {
type = Class.forName(fieldType, false, cl);
} catch (ClassNotFoundException ex) {
// TODO Auto-generated catch block
throw new ComponentGleanerException("Can't load class " + fieldType);
}
ComponentRequirement requirement;
if (isRequirementListType(type)) {
requirement = new ComponentRequirementList();
String[] hints = anno.hints();
if (hints != null && hints.length > 0) {
((ComponentRequirementList)requirement).setRoleHints(Arrays.asList(hints));
}
//
// TODO: See if we can glean any type details out of any generic information from the map or collection
//
}
else {
requirement = new ComponentRequirement();
requirement.setRoleHint(filterEmptyAsNull(anno.hint()));
}
// TODO need to read default annotation values
// if (anno.role()==null || anno.role().isAssignableFrom(Object.class)) {
if (anno.role().isAssignableFrom(Object.class)) {
requirement.setRole(type.getName());
}
else {
requirement.setRole(anno.role().getName());
}
requirement.setFieldName(field.getName());
requirement.setFieldMappingType(type.getName());
return requirement;
}
private PlexusConfiguration findConfiguration(AnnField field, AnnClass c, ClassLoader cl) {
assert field != null;
Configuration anno = field.getAnnotation(Configuration.class);
if (anno == null) {
return null;
}
String name = filterEmptyAsNull(anno.name());
if (name == null) {
name = field.getName();
}
name = deHump(name);
XmlPlexusConfiguration config = new XmlPlexusConfiguration(name);
String value = filterEmptyAsNull(anno.value());
if (value != null) {
config.setValue(value);
}
return config;
}
}