001// Copyright 2012-2013 The Apache Software Foundation 002// 003// Licensed under the Apache License, Version 2.0 (the "License"); 004// you may not use this file except in compliance with the License. 005// You may obtain a copy of the License at 006// 007// http://www.apache.org/licenses/LICENSE-2.0 008// 009// Unless required by applicable law or agreed to in writing, software 010// distributed under the License is distributed on an "AS IS" BASIS, 011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012// See the License for the specific language governing permissions and 013// limitations under the License. 014 015package org.apache.tapestry5.internal.clojure; 016 017import clojure.lang.IFn; 018import clojure.lang.RT; 019import clojure.lang.Symbol; 020import clojure.lang.Var; 021import org.apache.tapestry5.clojure.ClojureBuilder; 022import org.apache.tapestry5.clojure.MethodToFunctionSymbolMapper; 023import org.apache.tapestry5.clojure.Namespace; 024import org.apache.tapestry5.ioc.OperationTracker; 025import org.apache.tapestry5.ioc.services.Builtin; 026import org.apache.tapestry5.ioc.services.PlasticProxyFactory; 027import org.apache.tapestry5.ioc.util.ExceptionUtils; 028import org.apache.tapestry5.plastic.*; 029 030import java.lang.reflect.Method; 031 032public class ClojureBuilderImpl implements ClojureBuilder 033{ 034 private final PlasticProxyFactory proxyFactory; 035 036 private final MethodToFunctionSymbolMapper mapper; 037 038 private final OperationTracker tracker; 039 040 private final Var REQUIRE = RT.var("clojure.core", "require"); 041 042 public ClojureBuilderImpl(@Builtin PlasticProxyFactory proxyFactory, MethodToFunctionSymbolMapper mapper, OperationTracker tracker) 043 { 044 this.proxyFactory = proxyFactory; 045 this.mapper = mapper; 046 this.tracker = tracker; 047 } 048 049 public <T> T build(final Class<T> interfaceType) 050 { 051 assert interfaceType != null; 052 assert interfaceType.isInterface(); 053 054 Namespace annotation = interfaceType.getAnnotation(Namespace.class); 055 056 if (annotation == null) 057 { 058 throw new IllegalArgumentException(String.format("Interface type %s does not have the Namespace annotation.", 059 interfaceType.getName())); 060 } 061 062 final String namespace = annotation.value(); 063 064 ClassInstantiator<T> instantiator = proxyFactory.createProxy(interfaceType, new PlasticClassTransformer() 065 { 066 public void transform(PlasticClass plasticClass) 067 { 068 for (final Method m : interfaceType.getMethods()) 069 { 070 bridgeToClojure(plasticClass, m); 071 } 072 } 073 074 private void bridgeToClojure(final PlasticClass plasticClass, final Method method) 075 { 076 final MethodDescription desc = new MethodDescription(method); 077 078 if (method.getReturnType() == void.class) 079 { 080 throw new IllegalArgumentException(String.format("Method %s may not be void when bridging to Clojure functions.", 081 desc)); 082 } 083 084 final Symbol symbol = mapper.mapMethod(namespace, method); 085 086 tracker.run(String.format("Mapping %s method %s to Clojure function %s", 087 interfaceType.getName(), 088 desc.toShortString(), 089 symbol.toString()), new Runnable() 090 { 091 public void run() 092 { 093 Symbol namespaceSymbol = Symbol.create(symbol.getNamespace()); 094 095 REQUIRE.invoke(namespaceSymbol); 096 097 Var var = Var.find(symbol); 098 099 final PlasticField varField = plasticClass.introduceField(Var.class, method.getName() + "Var").inject(var); 100 101 plasticClass.introduceMethod(desc).changeImplementation(new InstructionBuilderCallback() 102 { 103 public void doBuild(InstructionBuilder builder) 104 { 105 bridgeToClojure(builder, desc, varField); 106 } 107 }); 108 109 } 110 }); 111 112 } 113 114 private void bridgeToClojure(InstructionBuilder builder, MethodDescription description, PlasticField varField) 115 { 116 builder.loadThis().getField(varField); 117 118 int count = description.argumentTypes.length; 119 120 Class[] invokeParameterTypes = new Class[count]; 121 122 for (int i = 0; i < count; i++) 123 { 124 invokeParameterTypes[i] = Object.class; 125 126 builder.loadArgument(i).boxPrimitive(description.argumentTypes[i]); 127 } 128 129 Method ifnMethod = null; 130 131 try 132 { 133 ifnMethod = IFn.class.getMethod("invoke", invokeParameterTypes); 134 } catch (NoSuchMethodException ex) 135 { 136 throw new RuntimeException(String.format("Unable to find correct IFn.invoke() method: %s", 137 ExceptionUtils.toMessage(ex)), ex); 138 } 139 140 builder.invoke(ifnMethod); 141 142 builder.castOrUnbox(description.returnType); 143 builder.returnResult(); 144 } 145 }); 146 147 return instantiator.newInstance(); 148 } 149}