/* * 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); } }