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