/*
* Copyright 2006-2008 Web Cohesion
*
* 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.enunciate.contract.jaxb.adapters;
import com.sun.mirror.apt.AnnotationProcessorEnvironment;
import com.sun.mirror.declaration.ClassDeclaration;
import com.sun.mirror.declaration.TypeDeclaration;
import com.sun.mirror.declaration.TypeParameterDeclaration;
import com.sun.mirror.type.*;
import com.sun.mirror.util.Types;
import net.sf.jelly.apt.Context;
import net.sf.jelly.apt.decorations.type.DecoratedClassType;
import net.sf.jelly.apt.decorations.type.DecoratedTypeMirror;
import net.sf.jelly.apt.decorations.TypeMirrorDecorator;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import java.util.Collection;
import java.util.Iterator;
import org.codehaus.enunciate.contract.validation.ValidationException;
/**
* A type mirror that mirrors an {@link javax.xml.bind.annotation.adapters.XmlAdapter}.
*
* @author Ryan Heaton
*/
public class AdapterType extends DecoratedClassType {
private final ReferenceType adaptedType;
private final TypeMirror adaptingType;
public AdapterType(ClassType adapterType) {
super(adapterType);
ClassDeclaration adapterDeclaration = adapterType.getDeclaration();
ClassType adaptorInterfaceType = findXmlAdapterType(adapterDeclaration);
if (adaptorInterfaceType == null) {
throw new ValidationException(adapterDeclaration.getPosition(), adapterDeclaration.getQualifiedName() + " is not an instance of javax.xml.bind.annotation.adapters.XmlAdapter.");
}
Collection<TypeMirror> adaptorTypeArgs = adaptorInterfaceType.getActualTypeArguments();
if ((adaptorTypeArgs == null) || (adaptorTypeArgs.size() != 2)) {
throw new ValidationException(adapterDeclaration.getPosition(), adapterDeclaration.getQualifiedName() +
" must specify both a value type and a bound type.");
}
Iterator<TypeMirror> formalTypeIt = adaptorTypeArgs.iterator();
this.adaptingType = TypeMirrorDecorator.decorate(formalTypeIt.next());
TypeMirror boundTypeMirror = formalTypeIt.next();
if (!(boundTypeMirror instanceof ReferenceType)) {
throw new ValidationException(adapterDeclaration.getPosition(), adapterDeclaration.getQualifiedName() + ": illegal XML adapter: not adapting a reference type (" + boundTypeMirror + ").");
}
else while (boundTypeMirror instanceof TypeVariable) {
//unwrap the type variable to find the bounds.
TypeParameterDeclaration declaration = ((TypeVariable) boundTypeMirror).getDeclaration();
if (declaration == null) {
throw new IllegalStateException(adapterDeclaration.getQualifiedName() + ": unable to find type parameter declaration for type variable " + boundTypeMirror + ".");
}
else if (declaration.getBounds() != null && !declaration.getBounds().isEmpty()) {
boundTypeMirror = declaration.getBounds().iterator().next();
}
else {
AnnotationProcessorEnvironment env = Context.getCurrentEnvironment();
boundTypeMirror = env.getTypeUtils().getDeclaredType(env.getTypeDeclaration(Object.class.getName()));
}
}
this.adaptedType = TypeMirrorDecorator.decorate((ReferenceType) boundTypeMirror);
}
/**
* Finds the interface type that declares that the specified declaration implements XmlAdapter.
*
* @param declaration The declaration.
* @return The interface type, or null if none found.
*/
private static ClassType findXmlAdapterType(ClassDeclaration declaration) {
if (Object.class.getName().equals(declaration.getQualifiedName())) {
return null;
}
ClassType superClass = declaration.getSuperclass();
if (XmlAdapter.class.getName().equals(superClass.getDeclaration().getQualifiedName())) {
return superClass;
}
return findXmlAdapterType(superClass.getDeclaration());
}
/**
* Whether this adapter can adapt the specified type.
*
* @param type The type.
* @return Whether this adapter can adapt the specified type.
*/
public boolean canAdapt(ReferenceType type) {
DecoratedTypeMirror decorated = (DecoratedTypeMirror) TypeMirrorDecorator.decorate(type);
ReferenceType adaptedType = getAdaptedType();
if (adaptedType instanceof DeclaredType) {
return ((DeclaredType)adaptedType).getDeclaration() != null && decorated.isInstanceOf(((DeclaredType)adaptedType).getDeclaration().getQualifiedName());
}
else if (adaptedType instanceof ArrayType) {
if (decorated instanceof ArrayType) {
TypeMirror adaptedComponentType = ((ArrayType) adaptedType).getComponentType();
while (adaptedComponentType instanceof DecoratedTypeMirror) {
adaptedComponentType = ((DecoratedTypeMirror) adaptedComponentType).getDelegate();
}
TypeMirror componentType = ((ArrayType) decorated).getComponentType();
while (componentType instanceof DecoratedTypeMirror) {
componentType = ((DecoratedTypeMirror) componentType).getDelegate();
}
Types typeUtils = Context.getCurrentEnvironment().getTypeUtils();
return typeUtils.isAssignable(componentType, adaptedComponentType);
}
}
return false;
}
/**
* Get the adapting type for the specified type. This method differs from {@link #getAdaptingType()} because it takes
* into account whether the adapted is an array or collection.
*
* @param adaptedType The type.
* @return The adapting type, or null if not adaptable.
*/
public TypeMirror getAdaptingType(TypeMirror adaptedType) {
DecoratedTypeMirror decorated = (DecoratedTypeMirror) TypeMirrorDecorator.decorate(adaptedType);
TypeMirror componentType = null;
if (decorated.isCollection()) {
Iterator<TypeMirror> itemTypes = ((DeclaredType) decorated).getActualTypeArguments().iterator();
if (!itemTypes.hasNext()) {
AnnotationProcessorEnvironment env = Context.getCurrentEnvironment();
Types typeUtils = env.getTypeUtils();
componentType = TypeMirrorDecorator.decorate(typeUtils.getDeclaredType(env.getTypeDeclaration(java.lang.Object.class.getName())));
}
else {
componentType = itemTypes.next();
}
}
else if (decorated.isArray()) {
componentType = ((ArrayType) decorated).getComponentType();
}
if (componentType instanceof ReferenceType && canAdapt((ReferenceType) componentType)) {
//if we can adapt the component type, then the adapting type is the collection of the declared adapting type.
AnnotationProcessorEnvironment env = Context.getCurrentEnvironment();
TypeDeclaration collection = env.getTypeDeclaration(java.util.Collection.class.getName());
TypeMirror declaredAdapting = getAdaptingType();
while (declaredAdapting instanceof DecoratedTypeMirror) {
declaredAdapting = ((DecoratedTypeMirror) declaredAdapting).getDelegate();
}
return env.getTypeUtils().getDeclaredType(collection, declaredAdapting);
}
else {
return getAdaptingType();
}
}
/**
* The type that is being adapted by this adapter.
*
* @return The type that is being adapted by this adapter.
*/
public ReferenceType getAdaptedType() {
return adaptedType;
}
/**
* The type to which this adapter is adapting.
*
* @return The type to which this adapter is adapting.
*/
public TypeMirror getAdaptingType() {
return adaptingType;
}
}