/*
* Copyright (C) 2015 Red Hat, Inc. and/or its affiliates.
*
* 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.errai.ioc.rebind.ioc.injector.api;
import static org.apache.commons.lang3.Validate.notNull;
import static org.jboss.errai.codegen.util.PrivateAccessUtil.getPrivateFieldAccessorName;
import static org.jboss.errai.codegen.util.PrivateAccessUtil.getPrivateMethodName;
import static org.jboss.errai.codegen.util.Stmt.invokeStatic;
import static org.jboss.errai.codegen.util.Stmt.loadStatic;
import static org.jboss.errai.codegen.util.Stmt.loadVariable;
import static org.jboss.errai.codegen.util.Stmt.nestedCall;
import static org.jboss.errai.ioc.rebind.ioc.bootstrapper.FactoryGenerator.getLocalVariableName;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import javax.enterprise.context.Dependent;
import org.jboss.errai.codegen.Context;
import org.jboss.errai.codegen.Statement;
import org.jboss.errai.codegen.builder.ClassStructureBuilder;
import org.jboss.errai.codegen.builder.ContextualStatementBuilder;
import org.jboss.errai.codegen.meta.HasAnnotations;
import org.jboss.errai.codegen.meta.MetaClass;
import org.jboss.errai.codegen.meta.MetaClassMember;
import org.jboss.errai.codegen.meta.MetaField;
import org.jboss.errai.codegen.meta.MetaMethod;
import org.jboss.errai.codegen.meta.MetaParameter;
import org.jboss.errai.codegen.meta.impl.build.BuildMetaClass;
import org.jboss.errai.codegen.util.CDIAnnotationUtils;
import org.jboss.errai.ioc.client.api.CodeDecorator;
import org.jboss.errai.ioc.client.container.Factory;
import org.jboss.errai.ioc.rebind.ioc.graph.api.Injectable;
/**
* Contains metadata for an element of a particular {@link DecorableType}.
*
* {@link CodeDecorator Code decorators} can use this instance to inspect
* annotations and other information regarding a decorable element.
*
* This type also has methods for generating code to access the decorable type
* within the context of certain {@link Factory} methods.
*
* @author Max Barkley <mbarkley@redhat.com>
*/
public class Decorable {
/**
* The kinds of decorable elements.
*
* @author Max Barkley <mbarkley@redhat.com>
*/
public enum DecorableType {
FIELD {
@Override
public MetaClass getType(final HasAnnotations annotated) {
return ((MetaField) annotated).getType();
}
@Override
public MetaClass getEnclosingType(final HasAnnotations annotated) {
return ((MetaField) annotated).getDeclaringClass();
}
@Override
public ContextualStatementBuilder getAccessStatement(final HasAnnotations annotated, final BuildMetaClass factory) {
return call(loadVariable("instance"), annotated, factory);
}
@Override
public ContextualStatementBuilder call(final Statement instance, final HasAnnotations annotated,
final BuildMetaClass factory, final Statement... params) {
final MetaField field = (MetaField) annotated;
if (field.isPublic()) {
if (field.isStatic()) {
return loadStatic(field.getDeclaringClass(), field.getName());
} else {
return nestedCall(instance).loadField(field);
}
} else {
final Object[] accessorParams = (field.isStatic() ? new Object[0] : new Object[] { instance });
return invokeStatic(notNull(factory), getPrivateFieldAccessorName(field), accessorParams);
}
}
},
METHOD {
@Override
public MetaClass getType(final HasAnnotations annotated) {
return ((MetaMethod) annotated).getReturnType();
}
@Override
public MetaClass getEnclosingType(final HasAnnotations annotated) {
return ((MetaMethod) annotated).getDeclaringClass();
}
@Override
public ContextualStatementBuilder getAccessStatement(final HasAnnotations annotated, final BuildMetaClass factory, final Statement[] statement) {
return call(loadVariable("instance"), annotated, factory, statement);
}
@Override
public ContextualStatementBuilder getAccessStatement(final HasAnnotations annotated, final BuildMetaClass factory) {
return getAccessStatement(annotated, factory, new Statement[0]);
}
@Override
public ContextualStatementBuilder call(final Statement instance, final HasAnnotations annotated, final BuildMetaClass factory,
final Statement... statement) {
final MetaMethod method = (MetaMethod) annotated;
if (method.isPublic()) {
if (method.isStatic()) {
return invokeStatic(method.getDeclaringClass(), method.getName(), (Object[]) statement);
} else {
return nestedCall(instance).invoke(method, (Object[]) statement);
}
} else {
final Object[] params = getParams(method.isStatic(), instance, statement);
return invokeStatic(notNull(factory), getPrivateMethodName(method), params);
}
}
private Object[] getParams(final boolean isStatic, final Statement instance, final Statement... statement) {
final int offset = (isStatic ? 0 : 1);
final Object[] params = new Object[statement.length+offset];
if (!isStatic) {
params[0] = instance;
}
for (int i = 0; i < statement.length; i++) {
params[i+offset] = statement[i];
}
return params;
}
},
PARAM {
@Override
public MetaClass getType(final HasAnnotations annotated) {
return ((MetaParameter) annotated).getType();
}
@Override
public MetaClass getEnclosingType(final HasAnnotations annotated) {
return ((MetaParameter) annotated).getDeclaringMember().getDeclaringClass();
}
@Override
public ContextualStatementBuilder getAccessStatement(final HasAnnotations annotated, final BuildMetaClass factory) {
final MetaParameter param = (MetaParameter) annotated;
return loadVariable(getLocalVariableName(param));
}
@Override
public ContextualStatementBuilder call(final Statement instance, final HasAnnotations annotated,
final BuildMetaClass factory, final Statement... params) {
return METHOD.call(instance, ((MetaParameter) annotated).getDeclaringMember(), factory, params);
}
@Override
public ContextualStatementBuilder call(final HasAnnotations annotated, final BuildMetaClass factory, final Statement... params) {
return call(loadVariable("instance"), annotated, factory, params);
}
@Override
public String getName(final HasAnnotations annotated) {
return ((MetaParameter) annotated).getName();
}
},
TYPE {
@Override
public MetaClass getType(final HasAnnotations annotated) {
return ((MetaClass) annotated);
}
@Override
public MetaClass getEnclosingType(final HasAnnotations annotated) {
return ((MetaClass) annotated);
}
@Override
public ContextualStatementBuilder getAccessStatement(final HasAnnotations annotated, final BuildMetaClass factory) {
return loadVariable("instance");
}
@Override
public String getName(final HasAnnotations annotated) {
return ((MetaClass) annotated).getName();
}
@Override
public ContextualStatementBuilder call(final Statement instance, final HasAnnotations annotated, final BuildMetaClass factory,
final Statement... params) {
return nestedCall(instance);
}
};
public abstract MetaClass getType(HasAnnotations annotated);
public abstract MetaClass getEnclosingType(HasAnnotations annotated);
public ContextualStatementBuilder getAccessStatement(HasAnnotations annotated, final BuildMetaClass factory, final Statement[] params) {
return getAccessStatement(annotated, factory);
}
public abstract ContextualStatementBuilder getAccessStatement(final HasAnnotations annotated, final BuildMetaClass factory);
public abstract ContextualStatementBuilder call(final Statement instance, final HasAnnotations annotated, final BuildMetaClass factory, Statement... params);
public ContextualStatementBuilder call(final HasAnnotations annotated, final BuildMetaClass factory, Statement... params) {
return getAccessStatement(annotated, factory, params);
}
public String getName(HasAnnotations annotated) {
return ((MetaClassMember) annotated).getName();
}
public static DecorableType fromElementType(ElementType elemType) {
switch (elemType) {
case FIELD:
return DecorableType.FIELD;
case METHOD:
return DecorableType.METHOD;
case PARAMETER:
return DecorableType.PARAM;
case TYPE:
return DecorableType.TYPE;
default:
throw new RuntimeException("Unsupported element type " + elemType);
}
}
}
private final HasAnnotations annotated;
private final Annotation annotation;
private final DecorableType decorableType;
private final InjectionContext injectionContext;
private final Context context;
private final BuildMetaClass factory;
private final Injectable injectable;
public Decorable(final HasAnnotations annotated, final Annotation annotation, final DecorableType decorableType,
final InjectionContext injectionContext, final Context context, final BuildMetaClass factory, final Injectable injectable) {
this.annotated = annotated;
this.annotation = annotation;
this.decorableType = decorableType;
this.injectionContext = injectionContext;
this.context = context;
this.factory = factory;
this.injectable = injectable;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Decorable)) {
return false;
}
final Decorable other = (Decorable) obj;
return decorableType.equals(other.decorableType) && CDIAnnotationUtils.equals(annotation, other.annotation)
&& annotated.equals(other.annotated) && injectable.equals(other.injectable);
}
@Override
public int hashCode() {
return decorableType.hashCode() ^ CDIAnnotationUtils.hashCode(annotation) ^ annotated.hashCode() ^ injectable.hashCode();
}
/**
* @return The annotation relevant to the {@link CodeDecorator} for which this {@link Decorable} was created.
*/
public Annotation getAnnotation() {
return annotation;
}
/**
* @return The declaring type of the decorable element. If the decorable element is a type, then this returns that type.
*/
public MetaClass getDecorableDeclaringType() {
return decorableType().getEnclosingType(annotated);
}
/**
* @return The type of the decorable element. The type of a field or parameter, the return type of a method, the type itself for a type.
*/
public MetaClass getType() {
return decorableType().getType(annotated);
}
/**
* For all but parameters, this access statement can be used in generated
* implementations for
* {@link Factory#createInstance(org.jboss.errai.ioc.client.container.ContextManager)}
* and
* {@link Factory#destroyInstance(Object, org.jboss.errai.ioc.client.container.ContextManager)}
* . For parameters it is only valid in the former.
*
* @param params
* If this decorable is a method these parameters are used in the
* generated invocation. Otherwise they are ignored.
* @return A reference to the decorated value. For fields or methods, the
* member is loaded/invoked. For types a reference to the constructed
* type. For parameters, a reference to a local variable of the value that was passed in to the parameter.
*/
public Statement getAccessStatement(Statement... params) {
return decorableType().getAccessStatement(annotated, factory, params);
}
/**
* @return The annotated element of this decorable.
*/
public HasAnnotations get() {
return annotated;
}
/**
* @return The context of the {@link ClassStructureBuilder} of the
* {@link Factory} being generated.
*/
public Context getCodegenContext() {
return context;
}
/**
* @return The value of {@link #get()} cast as a field.
*/
public MetaMethod getAsMethod() {
return (MetaMethod) annotated;
}
/**
* @return The kind of this decorable.
*/
public DecorableType decorableType() {
return decorableType;
}
/**
* @return The shared injection context used to generate all factories.
*/
public InjectionContext getInjectionContext() {
return injectionContext;
}
/**
* @return The value of {@link #get()} cast as a parameter.
*/
public MetaParameter getAsParameter() {
return (MetaParameter) annotated;
}
/**
* @return The value of {@link #get()} cast as a field.
*/
public MetaField getAsField() {
return (MetaField) annotated;
}
/*
* Behaves differently than getAccessStatement for parameters,
* where the method is called.
*/
public Statement call(final Statement... values) {
return decorableType().call(annotated, factory, values);
}
/**
* @return The name of the annotated element. Calls
* {@link MetaClassMember#getName()} for fields and methods. The
* source code name for parameter, and the simple class name for
* types.
*/
public String getName() {
return decorableType().getName(annotated);
}
/**
* @return The injectable for the enclosing type.
*/
public Injectable getEnclosingInjectable() {
return injectable;
}
/**
* @return The {@link BuildMetaClass} for the factory being generated.
*/
public BuildMetaClass getFactoryMetaClass() {
return factory;
}
/**
* @return True iff the enclosing injectable is {@link Dependent} scoped.
*/
public boolean isEnclosingTypeDependent() {
return injectable.getWiringElementTypes().contains(WiringElementType.DependentBean);
}
}