/*
* Copyright (C) 2012 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.ui.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 static org.jboss.errai.codegen.util.Stmt.loadVariable;
import static org.jboss.errai.codegen.util.Stmt.nestedCall;
import static org.jboss.errai.codegen.util.Stmt.newObject;
import java.util.ArrayList;
import java.util.List;
import org.jboss.errai.codegen.Parameter;
import org.jboss.errai.codegen.Statement;
import org.jboss.errai.codegen.builder.impl.ObjectBuilder;
import org.jboss.errai.codegen.meta.MetaClassFactory;
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.databinding.client.PropertyChangeUnsubscribeHandle;
import org.jboss.errai.databinding.rebind.DataBindingUtil;
import org.jboss.errai.ioc.client.api.CodeDecorator;
import org.jboss.errai.ioc.client.container.Factory;
import org.jboss.errai.ioc.client.container.RefHolder;
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 org.jboss.errai.ui.shared.TemplateUtil;
import org.jboss.errai.ui.shared.TemplateWidgetMapper;
import org.jboss.errai.ui.shared.api.annotations.Templated;
import org.jboss.errai.ui.shared.api.annotations.style.StyleBinding;
import org.jboss.errai.ui.shared.api.style.BindingRegistrationHandle;
import org.jboss.errai.ui.shared.api.style.StyleBindingChangeHandler;
import org.jboss.errai.ui.shared.api.style.StyleBindingExecutor;
import org.jboss.errai.ui.shared.api.style.StyleBindingsRegistry;
import com.google.gwt.dom.client.Style;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.Widget;
/**
* @author Mike Brock
*/
@CodeDecorator
@SuppressWarnings("deprecation")
public class StyleBindingCodeDecorator extends IOCDecoratorExtension<StyleBinding> {
private static final String DATA_BINDING_CONFIG_ATTR = "StyleBinding:DataBinderConfigured";
private static final String STYLE_BINDING_HOUSEKEEPING_ATTR = "StyleBinding:HousekeepingReg";
public StyleBindingCodeDecorator(Class<StyleBinding> decoratesWith) {
super(decoratesWith);
}
private static void bindHandlingMethod(final Decorable decorable,
final FactoryController controller, final MetaParameter parameter) {
final Statement elementAccessor;
if (MetaClassFactory.get(Element.class).isAssignableFrom(parameter.getType())) {
elementAccessor = Refs.get("element");
}
else if (MetaClassFactory.get(Style.class).isAssignableFrom(parameter.getType())) {
elementAccessor = Stmt.loadVariable("element").invoke("getStyle");
}
else {
throw new RuntimeException("illegal target type for style binding method: " + parameter.getType() +
"; expected Element or Style");
}
final String registrationHandleVar = "handleFor" + decorable.getName();
final List<Statement> bindExecStmts = new ArrayList<Statement>();
final boolean enclosingTypeIsDependent = decorable.isEnclosingTypeDependent();
if (!enclosingTypeIsDependent) {
bindExecStmts.add(declareFinalVariable("instance", decorable.getEnclosingInjectable().getInjectedType(),
castTo(decorable.getEnclosingInjectable().getInjectedType(),
invokeStatic(Factory.class, "maybeUnwrapProxy", controller.contextGetInstanceStmt()))));
}
String hanldeVarName = registrationHandleVar;
bindExecStmts.add(controller.setReferenceStmt(registrationHandleVar, loadVariable(hanldeVarName)));
bindExecStmts.add(decorable.getAccessStatement(elementAccessor));
final ObjectBuilder bindExec = newObject(StyleBindingExecutor.class)
.extend()
.publicOverridesMethod("invokeBinding", Parameter.of(Element.class, "element"))
.appendAll(bindExecStmts)
.finish()
.finish();
final List<Statement> initStmts = new ArrayList<Statement>();
final List<Statement> destructionStmts = new ArrayList<Statement>();
initStmts.add(declareFinalVariable(hanldeVarName, parameterizedAs(RefHolder.class, typeParametersOf(BindingRegistrationHandle.class)), newObject(RefHolder.class)));
initStmts.add(loadVariable(hanldeVarName).invoke("set", invokeStatic(StyleBindingsRegistry.class, "get")
.invoke("addStyleBinding", decorable.getAnnotation().annotationType(), bindExec)));
destructionStmts.add(Stmt
.castTo(BindingRegistrationHandle.class,
controller.getReferenceStmt(registrationHandleVar, RefHolder.class).invoke("get"))
.invoke("cleanup"));
if (enclosingTypeIsDependent) {
addCleanup(decorable, controller, destructionStmts);
controller.addInitializationStatements(initStmts);
} else {
controller.addFactoryInitializationStatements(initStmts);
}
controller.addDestructionStatements(destructionStmts);
}
private static void addCleanup(final Decorable decorable, final FactoryController controller, final List<Statement> destructionStmts) {
final DataBindingUtil.DataBinderRef dataBinder = DataBindingUtil.lookupDataBinderRef(decorable, controller);
if (!controller.hasAttribute(STYLE_BINDING_HOUSEKEEPING_ATTR)) {
destructionStmts.add(
Stmt.invokeStatic(StyleBindingsRegistry.class, "get").invoke("cleanAllForBean", Refs.get("instance")));
if (dataBinder != null) {
destructionStmts.add(controller.getReferenceStmt("styleBindingChangeHandlerUnsub", PropertyChangeUnsubscribeHandle.class).invoke("unsubscribe"));
}
controller.setAttribute(STYLE_BINDING_HOUSEKEEPING_ATTR, Boolean.TRUE);
}
}
@Override
public void generateDecorator(Decorable decorable, FactoryController controller) {
final Statement valueAccessor;
switch (decorable.decorableType()) {
case METHOD:
final MetaMethod method = decorable.getAsMethod();
final MetaParameter[] parameters = method.getParameters();
if (!method.getReturnType().isVoid() && parameters.length == 0) {
valueAccessor = decorable.getAccessStatement();
}
else if (method.getReturnType().isVoid() && parameters.length == 1) {
// this method returns void and accepts exactly one parm. assume it's a handler method.
bindHandlingMethod(decorable, controller, parameters[0]);
return;
}
else {
throw new RuntimeException("problem with style binding. method is not a valid binding " + method);
}
break;
case FIELD:
valueAccessor = decorable.getAccessStatement();
break;
case TYPE:
// for api annotations being on a type is allowed.
if (decorable.getAnnotation().annotationType().getPackage().getName().startsWith("org.jboss.errai")) {
return;
}
default:
throw new RuntimeException("problem with style binding. element target type is invalid: " + decorable.decorableType());
}
final DataBindingUtil.DataBinderRef dataBinder = DataBindingUtil.lookupDataBinderRef(decorable, controller);
final List<Statement> initStmts = new ArrayList<Statement>();
final List<Statement> destructionStmts = new ArrayList<Statement>();
if (dataBinder != null) {
if (!controller.hasAttribute(DATA_BINDING_CONFIG_ATTR)) {
final String handlerVarName = "bindingChangeHandler";
controller.setAttribute(DATA_BINDING_CONFIG_ATTR, Boolean.TRUE);
initStmts.add(controller.setReferenceStmt(handlerVarName, newObject(StyleBindingChangeHandler.class)));
// ERRAI-817 deferred initialization
initStmts.add(controller.setReferenceStmt("styleBindingChangeHandlerUnsub", nestedCall(dataBinder.getValueAccessor()).invoke("addPropertyChangeHandler",
controller.getReferenceStmt(handlerVarName, StyleBindingChangeHandler.class))));
}
}
// ERRAI-821 deferred initialization
if (decorable.getType().isAssignableTo(Widget.class)) {
initStmts.add(invokeStatic(StyleBindingsRegistry.class, "get")
.invoke("addElementBinding", Refs.get("instance"),
decorable.getAnnotation(),
nestedCall(valueAccessor).invoke("getElement")));
}
else if (decorable.getType().isAnnotationPresent(Templated.class)) {
initStmts.add(invokeStatic(StyleBindingsRegistry.class, "get")
.invoke("addElementBinding", Refs.get("instance"),
decorable.getAnnotation(),
Stmt.invokeStatic(TemplateWidgetMapper.class, "get", valueAccessor).invoke("getElement")));
}
else if (decorable.getType().isAssignableTo(com.google.gwt.dom.client.Element.class)
|| RebindUtil.isNativeJsType(decorable.getType()) || RebindUtil.isElementalIface(decorable.getType())) {
initStmts.add(invokeStatic(StyleBindingsRegistry.class, "get")
.invoke("addElementBinding", Refs.get("instance"),
decorable.getAnnotation(),
invokeStatic(TemplateUtil.class, "asDeprecatedElement", valueAccessor)));
} else {
throw new RuntimeException("Unrecognized type, " + decorable.getType().getFullyQualifiedName()
+ ", with style binding " + decorable.getAnnotation().annotationType().getName());
}
addCleanup(decorable, controller, destructionStmts);
controller.addInitializationStatements(initStmts);
controller.addDestructionStatements(destructionStmts);
}
}