/*
* Copyright (C) 2011 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.enterprise.rebind;
import static org.jboss.errai.codegen.meta.MetaClassFactory.parameterizedAs;
import static org.jboss.errai.codegen.meta.MetaClassFactory.typeParametersOf;
import static org.jboss.errai.codegen.util.Stmt.castTo;
import static org.jboss.errai.codegen.util.Stmt.declareFinalVariable;
import static org.jboss.errai.codegen.util.Stmt.invokeStatic;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.Any;
import org.jboss.errai.bus.client.ErraiBus;
import org.jboss.errai.bus.client.api.Subscription;
import org.jboss.errai.codegen.Context;
import org.jboss.errai.codegen.Parameter;
import org.jboss.errai.codegen.Statement;
import org.jboss.errai.codegen.builder.AnonymousClassStructureBuilder;
import org.jboss.errai.codegen.builder.BlockBuilder;
import org.jboss.errai.codegen.builder.ContextualStatementBuilder;
import org.jboss.errai.codegen.meta.MetaClass;
import org.jboss.errai.codegen.meta.MetaMethod;
import org.jboss.errai.codegen.meta.MetaParameter;
import org.jboss.errai.codegen.util.Refs;
import org.jboss.errai.codegen.util.Stmt;
import org.jboss.errai.config.rebind.EnvUtil;
import org.jboss.errai.enterprise.client.cdi.AbstractCDIEventCallback;
import org.jboss.errai.enterprise.client.cdi.EventQualifierSerializer;
import org.jboss.errai.enterprise.client.cdi.JsTypeEventObserver;
import org.jboss.errai.enterprise.client.cdi.api.CDI;
import org.jboss.errai.ioc.client.api.CodeDecorator;
import org.jboss.errai.ioc.client.container.Factory;
import org.jboss.errai.ioc.rebind.ioc.bootstrapper.InjectUtil;
import org.jboss.errai.ioc.rebind.ioc.extension.IOCDecoratorExtension;
import org.jboss.errai.ioc.rebind.ioc.injector.api.Decorable;
import org.jboss.errai.ioc.rebind.ioc.injector.api.FactoryController;
import jsinterop.annotations.JsType;
/**
* Generates the boiler plate for @Observes annotations use in GWT clients.<br/>
* Basically creates a subscription for a CDI event type that invokes on the annotated method.
*
* @author Heiko Braun <hbraun@redhat.com>
* @author Mike Brock <cbrock@redhat.com>
* @author Christian Sadilek <csadilek@redhat.com>
* @author Max Barkley <mbarkley@redhat.com>
*/
@CodeDecorator
public class ObservesExtension extends IOCDecoratorExtension<Observes> {
public ObservesExtension(final Class<Observes> decoratesWith) {
super(decoratesWith);
}
@Override
public void generateDecorator(final Decorable decorable, final FactoryController controller) {
if (!EventQualifierSerializer.isSet()) {
NonGwtEventQualifierSerializerGenerator.loadAndSetEventQualifierSerializer();
}
final Context ctx = decorable.getCodegenContext();
final MetaParameter parm = decorable.getAsParameter();
final MetaMethod method = (MetaMethod) parm.getDeclaringMember();
controller.ensureMemberExposed(parm);
final MetaClass eventType = parm.getType().asBoxed();
final String parmClassName = eventType.getFullyQualifiedName();
final List<Annotation> annotations = InjectUtil.extractQualifiers(parm);
final Annotation[] qualifiers = annotations.toArray(new Annotation[annotations.size()]);
final Set<String> qualifierNames = new HashSet<>(CDI.getQualifiersPart(qualifiers));
final boolean isEnclosingTypeDependent = enclosingTypeIsDependentScoped(decorable);
if (qualifierNames.contains(Any.class.getName())) {
qualifierNames.remove(Any.class.getName());
}
final MetaClass callBackType = parameterizedAs(AbstractCDIEventCallback.class, typeParametersOf(eventType));
AnonymousClassStructureBuilder callBack = Stmt.newObject(callBackType).extend();
BlockBuilder<AnonymousClassStructureBuilder> callBackBlock;
if (!qualifierNames.isEmpty()) {
callBackBlock = callBack.initialize();
for (final String qualifierName : qualifierNames) {
callBackBlock.append(Stmt.loadClassMember("qualifierSet").invoke("add", qualifierName));
}
callBack = callBackBlock.finish();
}
final List<Statement> callbackStatements = new ArrayList<>();
if (!isEnclosingTypeDependent) {
callbackStatements
.add(declareFinalVariable("instance", decorable.getDecorableDeclaringType(), castTo(decorable.getEnclosingInjectable().getInjectedType(),
invokeStatic(Factory.class, "maybeUnwrapProxy", controller.contextGetInstanceStmt()))));
}
callbackStatements.add(decorable.call(Refs.get("event")));
callBackBlock = callBack.publicOverridesMethod("fireEvent", Parameter.finalOf(eventType, "event"))
.appendAll(callbackStatements)
.finish()
.publicOverridesMethod("toString")
._(Stmt.load("Observer: " + parmClassName + " " + Arrays.toString(qualifiers)).returnValue());
final List<Statement> initStatements = new ArrayList<>();
final List<Statement> destroyStatements = new ArrayList<>();
final String subscrVar = method.getName() + "Subscription";
final String subscribeMethod;
if (eventType.isAnnotationPresent(JsType.class)) {
subscribeMethod = "subscribeJsType";
callBackBlock = getJsTypeSubscriptionCallback(decorable, controller);
}
else if (EnvUtil.isPortableType(eventType) && !EnvUtil.isLocalEventType(eventType)) {
subscribeMethod = "subscribe";
callBackBlock = getSubscriptionCallback(decorable, controller);
}
else {
subscribeMethod = "subscribeLocal";
callBackBlock = getSubscriptionCallback(decorable, controller);
}
final Statement subscribeStatement = Stmt.create(ctx).invokeStatic(CDI.class, subscribeMethod, parmClassName,
callBackBlock.finish().finish());
if (isEnclosingTypeDependent) {
initStatements.add(controller.setReferenceStmt(subscrVar, subscribeStatement));
destroyStatements.add(controller.getReferenceStmt(subscrVar, Subscription.class).invoke("remove"));
} else {
initStatements.add(subscribeStatement);
}
for (final Class<?> cls : EnvUtil.getAllPortableConcreteSubtypes(eventType.asClass())) {
if (!EnvUtil.isLocalEventType(cls)) {
final ContextualStatementBuilder routingSubStmt = Stmt.invokeStatic(ErraiBus.class, "get").invoke("subscribe",
CDI.getSubjectNameByType(cls.getName()), Stmt.loadStatic(CDI.class, "ROUTING_CALLBACK"));
if (isEnclosingTypeDependent) {
final String subscrHandle = subscrVar + "For" + cls.getSimpleName();
initStatements.add(controller.setReferenceStmt(subscrHandle, routingSubStmt));
destroyStatements.add(
Stmt.nestedCall(controller.getReferenceStmt(subscrHandle, Subscription.class)).invoke("remove"));
} else {
initStatements.add(routingSubStmt);
}
}
}
if (isEnclosingTypeDependent) {
controller.addInitializationStatements(initStatements);
controller.addDestructionStatements(destroyStatements);
} else {
controller.addFactoryInitializationStatements(initStatements);
}
}
private boolean enclosingTypeIsDependentScoped(final Decorable decorable) {
return decorable.isEnclosingTypeDependent();
}
private BlockBuilder<AnonymousClassStructureBuilder> getSubscriptionCallback(final Decorable decorable, final FactoryController controller) {
final MetaParameter parm = decorable.getAsParameter();
final MetaClass eventType = parm.getType().asBoxed();
final String parmClassName = eventType.getFullyQualifiedName();
final List<Annotation> annotations = InjectUtil.extractQualifiers(parm);
final Annotation[] qualifiers = annotations.toArray(new Annotation[annotations.size()]);
final Set<String> qualifierNames = new HashSet<>(CDI.getQualifiersPart(qualifiers));
final MetaClass callBackType = parameterizedAs(AbstractCDIEventCallback.class, typeParametersOf(eventType));
AnonymousClassStructureBuilder callBack = Stmt.newObject(callBackType).extend();
BlockBuilder<AnonymousClassStructureBuilder> callBackBlock;
if (!qualifierNames.isEmpty()) {
callBackBlock = callBack.initialize();
for (final String qualifierName : qualifierNames) {
callBackBlock.append(Stmt.loadClassMember("qualifierSet").invoke("add", qualifierName));
}
callBack = callBackBlock.finish();
}
final List<Statement> fireEventStmts = new ArrayList<>();
if (!decorable.isEnclosingTypeDependent()) {
fireEventStmts.add(Stmt.declareFinalVariable("instance", decorable.getEnclosingInjectable().getInjectedType(),
Stmt.invokeStatic(Factory.class, "maybeUnwrapProxy", controller.contextGetInstanceStmt())));
}
fireEventStmts.add(decorable.call(Refs.get("event")));
callBackBlock = callBack.publicOverridesMethod("fireEvent", Parameter.finalOf(eventType, "event"))
.appendAll(fireEventStmts)
.finish()
.publicOverridesMethod("toString")
._(Stmt.load("Observer: " + parmClassName + " " + Arrays.toString(qualifiers)).returnValue());
return callBackBlock;
}
private BlockBuilder<AnonymousClassStructureBuilder> getJsTypeSubscriptionCallback(final Decorable decorable, final FactoryController controller) {
final MetaParameter parm = decorable.getAsParameter();
final MetaClass eventType = parm.getType().asBoxed();
final String parmClassName = eventType.getFullyQualifiedName();
final MetaClass callBackType = parameterizedAs(JsTypeEventObserver.class, typeParametersOf(eventType));
final AnonymousClassStructureBuilder callBack = Stmt.newObject(callBackType).extend();
BlockBuilder<AnonymousClassStructureBuilder> callBackBlock;
final List<Statement> fireEventStmts = new ArrayList<>();
if (!decorable.isEnclosingTypeDependent()) {
fireEventStmts.add(Stmt.declareFinalVariable("instance", decorable.getEnclosingInjectable().getInjectedType(),
Stmt.invokeStatic(Factory.class, "maybeUnwrapProxy", controller.contextGetInstanceStmt())));
}
fireEventStmts.add(decorable.call(Refs.get("event")));
callBackBlock = callBack.publicOverridesMethod("onEvent", Parameter.finalOf(eventType, "event"))
.appendAll(fireEventStmts)
.finish()
.publicOverridesMethod("toString")
._(Stmt.load("JsTypeObserver: " + parmClassName).returnValue());
return callBackBlock;
}
}