/*
* Copyright 2013 eXo Platform SAS
*
* 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 juzu.impl.plugin.binding;
import juzu.impl.common.Name;
import juzu.impl.plugin.application.metamodel.ApplicationMetaModel;
import juzu.impl.plugin.application.metamodel.ApplicationMetaModelPlugin;
import juzu.impl.metamodel.AnnotationKey;
import juzu.impl.metamodel.AnnotationState;
import juzu.impl.compiler.ElementHandle;
import juzu.impl.compiler.MessageCode;
import juzu.impl.compiler.ProcessingContext;
import juzu.impl.common.JSON;
import juzu.inject.ProviderFactory;
import juzu.plugin.binding.Bindings;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.util.ElementFilter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/** @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a> */
public class BindingMetaModelPlugin extends ApplicationMetaModelPlugin {
/** . */
public static final MessageCode IMPLEMENTATION_NOT_ASSIGNABLE =
new MessageCode(
"BINDING_IMPLEMENTATION_NOT_ASSIGNABLE",
"The binding implementation type %1$s does not extend or implement the %2$s type");
/** . */
public static final MessageCode PROVIDER_NOT_ASSIGNABLE =
new MessageCode(
"BINDING_PROVIDER_NOT_ASSIGNABLE",
"The binding implementation type %1$s must provides a type %2$s that extends the %3$s type");
/** . */
public static final MessageCode IMPLEMENTATION_NOT_ABSTRACT =
new MessageCode(
"BINDING_PROVIDER_FACTORY_NOT_ABSTRACT",
"The binding implementation provider factory %1$s must not be abstract");
/** . */
public static final MessageCode PROVIDER_FACTORY_NOT_PUBLIC =
new MessageCode(
"BINDING_IMPLEMENTATION_NOT_PUBLIC",
"The binding implementation provider factory %1$s must be public");
/** . */
public static final MessageCode IMPLEMENTATION_INVALID_TYPE =
new MessageCode(
"BINDING_IMPLEMENTATION_INVALID_TYPE",
"The binding implementation provider factory %1$s must be a class");
/** . */
public static final MessageCode PROVIDER_FACTORY_NO_ZERO_ARG_CTOR =
new MessageCode(
"BINDING_PROVIDER_FACTORY_NO_ZERO_ARG_CTOR",
"The binding implementation provider factory %1$s must provides a public zero argument constructor");
/** . */
public static final MessageCode PROVIDER_FACTORY_NO_PUBLIC_CTOR =
new MessageCode(
"BINDING_PROVIDER_FACTORY_NO_PUBLIC_CTOR",
"The binding implementation provider factory %1$s must provides a public constructor");
/** . */
private static final Name BINDINGS = Name.create(Bindings.class);
/** . */
private Map<ElementHandle.Package, JSON> state = new HashMap<ElementHandle.Package, JSON>();
public BindingMetaModelPlugin() {
super("binding");
}
@Override
public Set<Class<? extends java.lang.annotation.Annotation>> init(ProcessingContext env) {
return Collections.<Class<? extends java.lang.annotation.Annotation>>singleton(Bindings.class);
}
@Override
public void processAnnotationAdded(ApplicationMetaModel metaModel, AnnotationKey key, AnnotationState added) {
ProcessingContext env = metaModel.model.processingContext;
//
TypeMirror providerFactoryTM = env.getTypeElement(ProviderFactory.class.getName()).asType();
TypeElement providerElt = env.getTypeElement("javax.inject.Provider");
DeclaredType providerTM = (DeclaredType)providerElt.asType();
TypeMirror rawProviderTM = env.erasure(providerTM);
//
List<Map<String, Object>> bindings = (List<Map<String, Object>>)added.get("value");
ArrayList<JSON> list = new ArrayList<JSON>();
if (bindings != null) {
for (Map<String, Object> binding : bindings) {
ElementHandle.Type bindingValue = (ElementHandle.Type)binding.get("value");
ElementHandle.Type bindingImplementation = (ElementHandle.Type)binding.get("implementation");
String scope = (String)binding.get("scope");
//
JSON bindingJSON = new JSON().set("value", bindingValue.getName().toString());
//
TypeElement valueElt = env.get(bindingValue);
TypeMirror valueTM = valueElt.asType();
//
if (bindingImplementation != null) {
TypeElement implementationElt = env.get(bindingImplementation);
DeclaredType implementationTM = (DeclaredType)implementationElt.asType();
// Check class
if (implementationElt.getKind() != ElementKind.CLASS) {
throw IMPLEMENTATION_INVALID_TYPE.failure(env.get(key.getElement()), providerElt.getQualifiedName());
}
//
Set<Modifier> modifiers = implementationElt.getModifiers();
// Check not abstract
if (modifiers.contains(Modifier.ABSTRACT)) {
throw IMPLEMENTATION_NOT_ABSTRACT.failure(env.get(key.getElement()), implementationElt.getQualifiedName());
}
//
if (env.isAssignable(implementationTM, providerFactoryTM)) {
// Check public
if (!modifiers.contains(Modifier.PUBLIC)) {
throw PROVIDER_FACTORY_NOT_PUBLIC.failure(env.get(key.getElement()), implementationElt.getQualifiedName());
}
// Find zero arg constructor
ExecutableElement emptyCtor = null;
for (ExecutableElement ctorElt : ElementFilter.constructorsIn(env.getAllMembers(implementationElt))) {
if (ctorElt.getParameters().isEmpty()) {
emptyCtor = ctorElt;
break;
}
}
// Validate constructor
if (emptyCtor == null) {
throw PROVIDER_FACTORY_NO_ZERO_ARG_CTOR.failure(env.get(key.getElement()), implementationElt.getQualifiedName());
}
if (!emptyCtor.getModifiers().contains(Modifier.PUBLIC)) {
throw PROVIDER_FACTORY_NO_PUBLIC_CTOR.failure(env.get(key.getElement()), implementationElt.getQualifiedName());
}
}
else if (env.isAssignable(implementationTM, rawProviderTM)) {
TypeVariable T = (TypeVariable)providerTM.getTypeArguments().get(0);
TypeMirror resolved = env.asMemberOf(implementationTM, T.asElement());
if (env.isAssignable(resolved, valueTM)) {
// OK
}
else {
throw PROVIDER_NOT_ASSIGNABLE.failure(
env.get(key.getElement()),
implementationElt.getQualifiedName(),
resolved,
valueElt.getQualifiedName());
}
}
else if (env.isAssignable(implementationTM, valueTM)) {
// OK
}
else {
throw IMPLEMENTATION_NOT_ASSIGNABLE.failure(
env.get(key.getElement()),
implementationElt.getQualifiedName(),
valueElt.getQualifiedName());
}
//
bindingJSON.set("implementation", bindingImplementation.getName().toString());
}
// Add the declared scope if any
if (scope != null) {
bindingJSON.set("scope", scope);
}
//
list.add(bindingJSON);
}
}
//
state.put(metaModel.getHandle(), new JSON().set("bindings", list));
}
@Override
public void destroy(ApplicationMetaModel application) {
state.remove(application.getHandle());
}
@Override
public JSON getDescriptor(ApplicationMetaModel application) {
return state.get(application.getHandle());
}
}