/* * Copyright 2011 cruxframework.org. * * 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.cruxframework.crux.core.rebind.ioc; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.cruxframework.crux.core.client.ioc.Inject; import org.cruxframework.crux.core.client.ioc.IoCResource.Scope; import org.cruxframework.crux.core.client.ioc.IocContainer; import org.cruxframework.crux.core.client.ioc.IocProvider; import org.cruxframework.crux.core.client.rpc.CruxRpcRequestBuilder; import org.cruxframework.crux.core.client.screen.DeviceAdaptive.Device; import org.cruxframework.crux.core.client.screen.views.ViewBindable; import org.cruxframework.crux.core.client.utils.EscapeUtils; import org.cruxframework.crux.core.config.ConfigurationFactory; import org.cruxframework.crux.core.ioc.IoCException; import org.cruxframework.crux.core.ioc.IocConfig; import org.cruxframework.crux.core.ioc.IocConfigImpl; import org.cruxframework.crux.core.ioc.IocContainerManager; import org.cruxframework.crux.core.rebind.AbstractProxyCreator; import org.cruxframework.crux.core.rebind.CruxGeneratorException; import org.cruxframework.crux.core.rebind.context.RebindContext; import org.cruxframework.crux.core.rebind.screen.View; import org.cruxframework.crux.core.utils.JClassUtils; import com.google.gwt.core.client.GWT; import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.core.ext.typeinfo.JField; import com.google.gwt.core.ext.typeinfo.JMethod; import com.google.gwt.core.ext.typeinfo.JParameter; import com.google.gwt.core.ext.typeinfo.JType; import com.google.gwt.core.ext.typeinfo.NotFoundException; import com.google.gwt.user.client.rpc.RemoteService; import com.google.gwt.user.client.rpc.ServiceDefTarget; import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; /** * @author Thiago da Rosa de Bustamante * */ public class IocContainerRebind extends AbstractProxyCreator { protected Map<String, IocConfig<?>> configurations; protected Device device; protected IocContainerManager iocContainerManager; protected JClassType remoteServiceType; protected final View view; protected JClassType viewBindableType; public IocContainerRebind(RebindContext context, View view, String device) { super(context, false); this.view = view; viewBindableType = context.getGeneratorContext().getTypeOracle().findType(ViewBindable.class.getCanonicalName()); remoteServiceType = context.getGeneratorContext().getTypeOracle().findType(RemoteService.class.getCanonicalName()); this.device = Device.valueOf(device); iocContainerManager = new IocContainerManager(context); configurations = iocContainerManager.getConfigurationsForView(view, this.device); } @Override public String getProxyQualifiedName() { return IocProvider.class.getPackage().getName()+"."+getProxySimpleName(); } @Override public String getProxySimpleName() { String className = (view != null ? view.getId():"")+"_"+device.toString(); className = className.replaceAll("[\\W]", "_"); return "IocContainer_"+className; } /** * * @param srcWriter * @param type * @param parentVariable * @param iocContainerVariable * @param view * @param device */ public void injectFieldsAndMethods(SourcePrinter srcWriter, JClassType type, String parentVariable, String iocContainerVariable, View view, Device device) { Map<String, IocConfig<?>> configurations = iocContainerManager.getConfigurationsForView(view, device); injectFieldsAndMethods(srcWriter, type, parentVariable, new HashSet<String>(), iocContainerVariable, configurations); } @Override protected void generateProxyContructor(SourcePrinter srcWriter) throws CruxGeneratorException { srcWriter.println("public "+getProxySimpleName()+"(View view){"); srcWriter.println("super(view);"); srcWriter.println("}"); } @Override protected void generateProxyMethods(SourcePrinter srcWriter) throws CruxGeneratorException { Iterator<String> classes = configurations.keySet().iterator(); while (classes.hasNext()) { String className = classes.next(); generateContainerInstatiationMethod(srcWriter, className); } } /** * @return */ protected String[] getImports() { String[] imports = new String[] { org.cruxframework.crux.core.client.screen.views.View.class.getCanonicalName(), GWT.class.getCanonicalName() }; return imports; } @Override protected SourcePrinter getSourcePrinter() { String packageName = IocProvider.class.getPackage().getName(); PrintWriter printWriter = context.getGeneratorContext().tryCreate(context.getLogger(), packageName, getProxySimpleName()); if (printWriter == null) { return null; } ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(packageName, getProxySimpleName()); String[] imports = getImports(); for (String imp : imports) { composerFactory.addImport(imp); } composerFactory.setSuperclass(IocContainer.class.getCanonicalName()); return new SourceCodePrinter(composerFactory.createSourceWriter(context.getGeneratorContext(), printWriter), context.getLogger()); } /** * * @param srcWriter * @param className */ private void generateContainerInstatiationMethod(SourcePrinter srcWriter, String className) { try { srcWriter.println("public "+className+" get"+className.replace('.', '_')+"("+Scope.class.getCanonicalName()+" scope, String subscope){"); JClassType type = JClassUtils.getType(context.getGeneratorContext().getTypeOracle(), className); IocConfigImpl<?> iocConfig = (IocConfigImpl<?>) configurations.get(className); Class<?> providerClass = iocConfig.getProviderClass(); if (providerClass != null) { srcWriter.println(className+" result = _getScope(scope).getValue(GWT.create("+providerClass.getCanonicalName()+".class), "+EscapeUtils.quote(className)+", subscope, "); generateFieldsPopulationCallback(srcWriter, type); srcWriter.println(");"); } else if (iocConfig.getToClass() != null) { srcWriter.println(className+" result = _getScope(scope).getValue(new "+IocProvider.class.getCanonicalName()+"<"+className+">(){"); srcWriter.println("public "+className+" get(){"); srcWriter.println("return GWT.create("+iocConfig.getToClass().getCanonicalName()+".class);"); srcWriter.println("}"); srcWriter.println("}, "+EscapeUtils.quote(className)+", subscope, "); generateFieldsPopulationCallback(srcWriter, type); srcWriter.println(");"); } else { srcWriter.println(className+" result = _getScope(scope).getValue(new "+IocProvider.class.getCanonicalName()+"<"+className+">(){"); srcWriter.println("public "+className+" get(){"); String instantiationClass = getInstantiationClass(className); JClassType instantiationType = context.getGeneratorContext().getTypeOracle().findType(instantiationClass); if (instantiationType == null) { throw new CruxGeneratorException("Can not found type: "+instantiationClass); } if (instantiationType.isAssignableTo(remoteServiceType) && ConfigurationFactory.getConfigurations().sendCruxViewNameOnClientRequests().equals("true")) { srcWriter.println(className + " ret = GWT.create("+instantiationClass+".class);"); srcWriter.println("(("+ServiceDefTarget.class.getCanonicalName() + ")ret).setRpcRequestBuilder(new " + CruxRpcRequestBuilder.class.getCanonicalName() + "(getBoundCruxViewId()));"); srcWriter.println("return ret;"); } else { srcWriter.println("return GWT.create("+instantiationClass+".class);"); } srcWriter.println("}"); srcWriter.println("}, "+EscapeUtils.quote(className)+", subscope, "); generateFieldsPopulationCallback(srcWriter, type); srcWriter.println(");"); } if (type.isAssignableTo(viewBindableType)) { srcWriter.println("if (scope != "+Scope.class.getCanonicalName()+ "."+Scope.SINGLETON.name()+" && result.getBoundCruxViewId() == null){"); srcWriter.println("result.bindCruxView(this.getBoundCruxViewId());"); srcWriter.println("}"); } srcWriter.println("return result;"); srcWriter.println("}"); } catch (NotFoundException e) { throw new IoCException("IoC Error Class ["+className+"] not found.", e); } } /** * * @param srcWriter * @param className */ private void generateFieldsPopulationCallback(SourcePrinter srcWriter, JClassType type) { String className = type.getQualifiedSourceName(); srcWriter.println("new IocScope.CreateCallback<"+className+">(){"); srcWriter.println("public void onCreate("+className+" newObject){"); injectFieldsAndMethods(srcWriter, type, "newObject", new HashSet<String>(), getProxySimpleName()+".this", configurations); srcWriter.println("}"); srcWriter.println("}"); } /** * * @param className * @return */ private String getInstantiationClass(String className) { if (className.endsWith("Async")) { String serviceInterface = className.substring(0, className.length() - 5); JClassType type = context.getGeneratorContext().getTypeOracle().findType(serviceInterface); if (type != null && type.isAssignableTo(remoteServiceType)) { return type.getQualifiedSourceName(); } } return className; } @SuppressWarnings("deprecation") private static String getFieldInjectionExpression(JField field, String iocContainerVariable, Map<String, IocConfig<?>> configurations) { Inject inject = field.getAnnotation(Inject.class); if (inject != null) { JType fieldType = field.getType(); if (!field.isStatic()) { if (fieldType.isClassOrInterface() != null) { String fieldTypeName = fieldType.getQualifiedSourceName(); IocConfigImpl<?> iocConfig = (IocConfigImpl<?>) configurations.get(fieldTypeName); if (iocConfig != null) { if (inject.scope().equals(org.cruxframework.crux.core.client.ioc.Inject.Scope.DEFAULT)) { return iocContainerVariable+".get"+fieldTypeName.replace('.', '_')+ "("+Scope.class.getCanonicalName()+"."+iocConfig.getScope().name()+", null)"; } return iocContainerVariable+".get"+fieldTypeName.replace('.', '_')+ "("+Scope.class.getCanonicalName()+"."+getScopeName(inject.scope())+", "+EscapeUtils.quote(inject.subscope())+")"; } else { return "GWT.create("+fieldTypeName+".class)"; } } else { throw new IoCException("Error injecting field ["+field.getName()+"] from type ["+field.getEnclosingType().getQualifiedSourceName()+"]. Primitive fields can not be handled by ioc container."); } } else { throw new IoCException("Error injecting field ["+field.getName()+"] from type ["+field.getEnclosingType().getQualifiedSourceName()+"]. Static fields can not be handled by ioc container."); } } return null; } private static Inject getInjectAnnotation(JParameter parameter) { Inject result = parameter.getAnnotation(Inject.class); if (result == null) { result = parameter.getEnclosingMethod().getAnnotation(Inject.class); } return result; } @SuppressWarnings("deprecation") private static String getParameterInjectionExpression(JParameter parameter, String iocContainerVariable, Map<String, IocConfig<?>> configurations) { JType parameterType = parameter.getType(); if (parameterType.isClassOrInterface() != null) { String fieldTypeName = parameterType.getQualifiedSourceName(); IocConfigImpl<?> iocConfig = (IocConfigImpl<?>) configurations.get(fieldTypeName); if (iocConfig != null) { Inject inject = getInjectAnnotation(parameter); if (inject.scope().equals(org.cruxframework.crux.core.client.ioc.Inject.Scope.DEFAULT)) { return iocContainerVariable+".get"+fieldTypeName.replace('.', '_')+ "("+Scope.class.getCanonicalName()+"."+iocConfig.getScope().name()+", null)"; } return iocContainerVariable+".get"+fieldTypeName.replace('.', '_')+ "("+Scope.class.getCanonicalName()+"."+getScopeName(inject.scope())+", "+EscapeUtils.quote(inject.subscope())+")"; } else { return "GWT.create("+fieldTypeName+".class)"; } } else { throw new IoCException("Error injecting parameter ["+parameter.getName()+"] from method ["+parameter.getEnclosingMethod().getReadableDeclaration()+"]. Primitive fields can not be handled by ioc container."); } } @SuppressWarnings("deprecation") private static String getScopeName(org.cruxframework.crux.core.client.ioc.Inject.Scope scope) { switch (scope) { case DOCUMENT: return Scope.SINGLETON.name(); case DEFAULT: return Scope.LOCAL.name(); default: return scope.name(); } } /** * * @param srcWriter * @param type * @param parentVariable * @param added * @param iocContainerVariable * @param configurations */ private static void injectFields(SourcePrinter srcWriter, JClassType type, String parentVariable, Set<String> added, String iocContainerVariable, Map<String, IocConfig<?>> configurations) { for (JField field : type.getFields()) { String fieldName = field.getName(); if (!added.contains(fieldName)) { added.add(fieldName); JType fieldType = field.getType(); if ((fieldType.isPrimitive()== null)) { String injectionExpression = getFieldInjectionExpression(field, iocContainerVariable, configurations); if (injectionExpression != null) { if (JClassUtils.isPropertyVisibleToWrite(type, field, false)) { if (JClassUtils.hasSetMethod(field, type)) { String setterMethodName = "set"+Character.toUpperCase(fieldName.charAt(0))+fieldName.substring(1); JMethod method = type.findMethod(setterMethodName, new JType[]{field.getType()}); if (method.getAnnotation(Inject.class) == null) // Annotated methods are handled apart { srcWriter.println(fieldType.getQualifiedSourceName()+" field_"+fieldName+" = "+ injectionExpression+";"); srcWriter.println(parentVariable+"."+setterMethodName+"(field_"+ fieldName+");"); } } else { srcWriter.println(parentVariable+"."+fieldName+" = "+ injectionExpression+";"); } } else { throw new IoCException("IoC Error Field ["+field.getName()+"] from class ["+type.getQualifiedSourceName()+"] is not a writeable property."); } } } } } } /** * * @param srcWriter * @param type * @param parentVariable * @param added * @param iocContainerVariable * @param configurations */ private static void injectFieldsAndMethods(SourcePrinter srcWriter, JClassType type, String parentVariable, Set<String> added, String iocContainerVariable, Map<String, IocConfig<?>> configurations) { injectFields(srcWriter, type, parentVariable, added, iocContainerVariable, configurations); injectMethods(srcWriter, type, parentVariable, added, iocContainerVariable, configurations); if (type.getSuperclass() != null) { injectFieldsAndMethods(srcWriter, type.getSuperclass(), parentVariable, added, iocContainerVariable, configurations); } } /** * * @param srcWriter * @param type * @param parentVariable * @param added * @param iocContainerVariable * @param configurations */ private static void injectMethods(SourcePrinter srcWriter, JClassType type, String parentVariable, Set<String> added, String iocContainerVariable, Map<String, IocConfig<?>> configurations) { for (JMethod method : type.getMethods()) { Inject inject = method.getAnnotation(Inject.class); if (inject != null && !method.isStatic()) { String methodName = method.getName(); if (!added.contains(methodName+"()")) { added.add(methodName+"()"); JParameter[] parameters = method.getParameters(); List<String> params = new ArrayList<String>(); for (JParameter parameter : parameters) { JType parameterType = parameter.getType(); if ((parameterType.isPrimitive()!= null)) { throw new IoCException("IoC Error Method ["+methodName+"] from class ["+type.getQualifiedSourceName()+"] declares an invalid parameter. Primitive types are not allowed here"); } String variableName = "parameter_"+methodName+"_"+parameter.getName(); params.add(variableName); String injectionExpression = getParameterInjectionExpression(parameter, iocContainerVariable, configurations); srcWriter.println(parameterType.getQualifiedSourceName()+" "+variableName+" = "+ injectionExpression+";"); } srcWriter.print(parentVariable+"."+methodName+"("); boolean first = true; for (String param : params) { if (!first) { srcWriter.print(", "); } first = false; srcWriter.print(param); } srcWriter.println(");"); } } } } }