Updated Branches: refs/heads/master 1761c896d -> f3ab646ca
TAP5-1944: Basic Clojure integration Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/f3ab646c Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/f3ab646c Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/f3ab646c Branch: refs/heads/master Commit: f3ab646ca8f03b4162fe1f80b2f11e1d2985b51a Parents: 1761c89 Author: Howard M. Lewis Ship <[email protected]> Authored: Sun Jun 3 09:35:17 2012 -0700 Committer: Howard M. Lewis Ship <[email protected]> Committed: Sun Jun 3 09:35:17 2012 -0700 ---------------------------------------------------------------------- settings.gradle | 2 +- tapestry-clojure/LICENSE-clojure.txt | 8 + tapestry-clojure/LICENSE.txt | 202 +++++++++++++++ tapestry-clojure/NOTICE.txt | 3 + tapestry-clojure/build.gradle | 14 + .../apache/tapestry5/clojure/ClojureBuilder.java | 32 +++ .../apache/tapestry5/clojure/ClojureModule.java | 53 ++++ .../org/apache/tapestry5/clojure/FunctionName.java | 42 +++ .../clojure/MethodToFunctionSymbolMapper.java | 38 +++ .../org/apache/tapestry5/clojure/Namespace.java | 41 +++ .../internal/clojure/AnnotationMapper.java | 44 +++ .../internal/clojure/ClojureBuilderImpl.java | 152 +++++++++++ .../tapestry5/internal/clojure/DefaultMapper.java | 77 ++++++ .../clojure/tests/AnnotationMapperSpec.groovy | 57 ++++ .../clojure/tests/ClojureBuilderSpec.groovy | 43 +++ .../tests/DefaultFunctionNameMapperSpec.groovy | 39 +++ .../apache/tapestry5/clojure/tests/Fixture.java | 15 + .../apache/tapestry5/clojure/tests/TestModule.java | 11 + tapestry-clojure/src/test/resources/fixture.clj | 6 + 19 files changed, 878 insertions(+), 1 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f3ab646c/settings.gradle ---------------------------------------------------------------------- diff --git a/settings.gradle b/settings.gradle index fdafe54..357585e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,5 @@ include "plastic", "tapestry5-annotations", "tapestry-test", "tapestry-func", "tapestry-ioc", "tapestry-json", "tapestry-core" include "tapestry-hibernate-core", "tapestry-hibernate", "tapestry-jmx", "tapestry-upload", "tapestry-spring" include "tapestry-beanvalidator", "tapestry-yuicompressor", "tapestry-jpa", "tapestry-kaptcha" -include "tapestry-javadoc", "quickstart" +include "tapestry-javadoc", "quickstart", "tapestry-clojure" http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f3ab646c/tapestry-clojure/LICENSE-clojure.txt ---------------------------------------------------------------------- diff --git a/tapestry-clojure/LICENSE-clojure.txt b/tapestry-clojure/LICENSE-clojure.txt new file mode 100644 index 0000000..4419939 --- /dev/null +++ b/tapestry-clojure/LICENSE-clojure.txt @@ -0,0 +1,8 @@ + * Clojure + * Copyright (c) Rich Hickey. All rights reserved. + * The use and distribution terms for this software are covered by the + * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) + * which can be found in the file epl-v10.html at the root of this distribution. + * By using this software in any fashion, you are agreeing to be bound by + * the terms of this license. + * You must not remove this notice, or any other, from this software. http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f3ab646c/tapestry-clojure/LICENSE.txt ---------------------------------------------------------------------- diff --git a/tapestry-clojure/LICENSE.txt b/tapestry-clojure/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/tapestry-clojure/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f3ab646c/tapestry-clojure/NOTICE.txt ---------------------------------------------------------------------- diff --git a/tapestry-clojure/NOTICE.txt b/tapestry-clojure/NOTICE.txt new file mode 100644 index 0000000..439eb83 --- /dev/null +++ b/tapestry-clojure/NOTICE.txt @@ -0,0 +1,3 @@ +This product includes software developed by +The Apache Software Foundation (http://www.apache.org/). + http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f3ab646c/tapestry-clojure/build.gradle ---------------------------------------------------------------------- diff --git a/tapestry-clojure/build.gradle b/tapestry-clojure/build.gradle new file mode 100644 index 0000000..055a53d --- /dev/null +++ b/tapestry-clojure/build.gradle @@ -0,0 +1,14 @@ +description = "Allows Clojure functions to be injected into Tapestry services and components" + +dependencies { + compile project(':tapestry-ioc') + provided "org.clojure:clojure:1.4.0" + + testCompile "org.spockframework:spock-core:${versions.spock}" +} + +jar { + manifest { + attributes 'Tapestry-Module-Classes': 'org.apache.tapestry5.clojure.ClojureModule' + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f3ab646c/tapestry-clojure/src/main/java/org/apache/tapestry5/clojure/ClojureBuilder.java ---------------------------------------------------------------------- diff --git a/tapestry-clojure/src/main/java/org/apache/tapestry5/clojure/ClojureBuilder.java b/tapestry-clojure/src/main/java/org/apache/tapestry5/clojure/ClojureBuilder.java new file mode 100644 index 0000000..02ac3a4 --- /dev/null +++ b/tapestry-clojure/src/main/java/org/apache/tapestry5/clojure/ClojureBuilder.java @@ -0,0 +1,32 @@ +// Copyright 2012 The Apache Software Foundation +// +// 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.apache.tapestry5.clojure; + +/** + * Creates a proxy for the interface that delegates each method to a Clojure function. + */ +public interface ClojureBuilder +{ + /** + * Creates the proxy. Method names are converted to Clojure function names. + * + * @param interfaceType + * type of interface, must have the {@link Namespace} annotation + * @param <T> + * @return the proxy + * @see MethodToFunctionSymbolMapper + */ + <T> T build(Class<T> interfaceType); +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f3ab646c/tapestry-clojure/src/main/java/org/apache/tapestry5/clojure/ClojureModule.java ---------------------------------------------------------------------- diff --git a/tapestry-clojure/src/main/java/org/apache/tapestry5/clojure/ClojureModule.java b/tapestry-clojure/src/main/java/org/apache/tapestry5/clojure/ClojureModule.java new file mode 100644 index 0000000..35697a7 --- /dev/null +++ b/tapestry-clojure/src/main/java/org/apache/tapestry5/clojure/ClojureModule.java @@ -0,0 +1,53 @@ +// Copyright 2012 The Apache Software Foundation +// +// 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.apache.tapestry5.clojure; + +import clojure.lang.RT; +import clojure.lang.Var; +import org.apache.tapestry5.internal.clojure.AnnotationMapper; +import org.apache.tapestry5.internal.clojure.ClojureBuilderImpl; +import org.apache.tapestry5.internal.clojure.DefaultMapper; +import org.apache.tapestry5.ioc.OrderedConfiguration; +import org.apache.tapestry5.ioc.ServiceBinder; +import org.apache.tapestry5.ioc.annotations.Contribute; +import org.apache.tapestry5.ioc.annotations.Startup; +import org.apache.tapestry5.ioc.services.ChainBuilder; + +import java.util.List; + +public class ClojureModule +{ + public static void bind(ServiceBinder binder) + { + binder.bind(ClojureBuilder.class, ClojureBuilderImpl.class); + } + + public static MethodToFunctionSymbolMapper buildMethodToFunctionMapper(List<MethodToFunctionSymbolMapper> configuration, ChainBuilder builder) + { + return builder.build(MethodToFunctionSymbolMapper.class, configuration); + } + + @Contribute(MethodToFunctionSymbolMapper.class) + public static void defaultMappers(OrderedConfiguration<MethodToFunctionSymbolMapper> configuration) + { + configuration.add("Annotation", new AnnotationMapper()); + configuration.add("Default", new DefaultMapper(), "after:*"); + } + + @Startup + public static void launchClojure() { + Var require = RT.var("clojure.core", "require"); + } +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f3ab646c/tapestry-clojure/src/main/java/org/apache/tapestry5/clojure/FunctionName.java ---------------------------------------------------------------------- diff --git a/tapestry-clojure/src/main/java/org/apache/tapestry5/clojure/FunctionName.java b/tapestry-clojure/src/main/java/org/apache/tapestry5/clojure/FunctionName.java new file mode 100644 index 0000000..0ad372c --- /dev/null +++ b/tapestry-clojure/src/main/java/org/apache/tapestry5/clojure/FunctionName.java @@ -0,0 +1,42 @@ +// Copyright 2012 The Apache Software Foundation +// +// 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.apache.tapestry5.clojure; + +import org.apache.tapestry5.ioc.annotations.UseWith; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.apache.tapestry5.ioc.annotations.AnnotationUseContext.SERVICE; + +/** + * Overrides the default mapping from method name to Clojure function name. + */ +@Target(ElementType.METHOD) +@Retention(RUNTIME) +@Documented +@UseWith(SERVICE) +public @interface FunctionName +{ + /** + * The name of the Clojure function to map the method to. This may be a simple name, in which case + * it will be a name within the {@link Namespace} of the service interface, or it may be a fully qualified name + * of an arbitrary Clojure function. + */ + String value(); +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f3ab646c/tapestry-clojure/src/main/java/org/apache/tapestry5/clojure/MethodToFunctionSymbolMapper.java ---------------------------------------------------------------------- diff --git a/tapestry-clojure/src/main/java/org/apache/tapestry5/clojure/MethodToFunctionSymbolMapper.java b/tapestry-clojure/src/main/java/org/apache/tapestry5/clojure/MethodToFunctionSymbolMapper.java new file mode 100644 index 0000000..1cd6fa4 --- /dev/null +++ b/tapestry-clojure/src/main/java/org/apache/tapestry5/clojure/MethodToFunctionSymbolMapper.java @@ -0,0 +1,38 @@ +// Copyright 2012 The Apache Software Foundation +// +// 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.apache.tapestry5.clojure; + +import clojure.lang.Symbol; +import org.apache.tapestry5.ioc.annotations.UsesOrderedConfiguration; + +import java.lang.reflect.Method; + +/** + * Maps a method from a service interface to a fully-qualified Clojure function name, as a Clojure + * {@link clojure.lang.Symbol}. This service is itself a chain of command, to support adding or overriding + * the mapping. + */ +@UsesOrderedConfiguration(MethodToFunctionSymbolMapper.class) +public interface MethodToFunctionSymbolMapper +{ + /** + * @param namespace + * namespace for the service (from {@link Namespace} annotation) + * @param method + * method for which a function name is desired. + * @return Symbol for this method, or null (to drop down to next mapper) + */ + Symbol mapMethod(String namespace, Method method); +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f3ab646c/tapestry-clojure/src/main/java/org/apache/tapestry5/clojure/Namespace.java ---------------------------------------------------------------------- diff --git a/tapestry-clojure/src/main/java/org/apache/tapestry5/clojure/Namespace.java b/tapestry-clojure/src/main/java/org/apache/tapestry5/clojure/Namespace.java new file mode 100644 index 0000000..5f2e01d --- /dev/null +++ b/tapestry-clojure/src/main/java/org/apache/tapestry5/clojure/Namespace.java @@ -0,0 +1,41 @@ +// Copyright 2012 The Apache Software Foundation +// +// 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.apache.tapestry5.clojure; + +import org.apache.tapestry5.ioc.annotations.UseWith; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.apache.tapestry5.ioc.annotations.AnnotationUseContext.SERVICE; + +/** + * Maps a service interface to a Clojure namespace. Each method is mapped to a corresponding {@link clojure.lang.Var} + * within the namespace (or as defined by a {@link FunctionName} annotation on the individual method). + */ +@Target(ElementType.TYPE) +@Retention(RUNTIME) +@Documented +@UseWith(SERVICE) +public @interface Namespace +{ + /** + * The fully qualified namespace that will contain the functions to be exposed as methods. + */ + String value(); +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f3ab646c/tapestry-clojure/src/main/java/org/apache/tapestry5/internal/clojure/AnnotationMapper.java ---------------------------------------------------------------------- diff --git a/tapestry-clojure/src/main/java/org/apache/tapestry5/internal/clojure/AnnotationMapper.java b/tapestry-clojure/src/main/java/org/apache/tapestry5/internal/clojure/AnnotationMapper.java new file mode 100644 index 0000000..8f99cfd --- /dev/null +++ b/tapestry-clojure/src/main/java/org/apache/tapestry5/internal/clojure/AnnotationMapper.java @@ -0,0 +1,44 @@ +// Copyright 2012 The Apache Software Foundation +// +// 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.apache.tapestry5.internal.clojure; + +import clojure.lang.Symbol; +import org.apache.tapestry5.clojure.FunctionName; +import org.apache.tapestry5.clojure.MethodToFunctionSymbolMapper; + +import java.lang.reflect.Method; + +public class AnnotationMapper implements MethodToFunctionSymbolMapper +{ + @Override + public Symbol mapMethod(String namespace, Method method) + { + FunctionName annotation = method.getAnnotation(FunctionName.class); + + if (annotation == null) + { + return null; + } + + String name = annotation.value(); + + if (name.contains("/")) + { + return Symbol.create(name); + } + + return Symbol.create(namespace, name); + } +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f3ab646c/tapestry-clojure/src/main/java/org/apache/tapestry5/internal/clojure/ClojureBuilderImpl.java ---------------------------------------------------------------------- diff --git a/tapestry-clojure/src/main/java/org/apache/tapestry5/internal/clojure/ClojureBuilderImpl.java b/tapestry-clojure/src/main/java/org/apache/tapestry5/internal/clojure/ClojureBuilderImpl.java new file mode 100644 index 0000000..c4d8c89 --- /dev/null +++ b/tapestry-clojure/src/main/java/org/apache/tapestry5/internal/clojure/ClojureBuilderImpl.java @@ -0,0 +1,152 @@ +// Copyright 2012 The Apache Software Foundation +// +// 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.apache.tapestry5.internal.clojure; + +import clojure.lang.IFn; +import clojure.lang.RT; +import clojure.lang.Symbol; +import clojure.lang.Var; +import org.apache.tapestry5.clojure.ClojureBuilder; +import org.apache.tapestry5.clojure.MethodToFunctionSymbolMapper; +import org.apache.tapestry5.clojure.Namespace; +import org.apache.tapestry5.ioc.OperationTracker; +import org.apache.tapestry5.ioc.internal.util.InternalUtils; +import org.apache.tapestry5.ioc.services.PlasticProxyFactory; +import org.apache.tapestry5.plastic.*; + +import java.lang.reflect.Method; + +public class ClojureBuilderImpl implements ClojureBuilder +{ + private final PlasticProxyFactory proxyFactory; + + private final MethodToFunctionSymbolMapper mapper; + + private final OperationTracker tracker; + + private final Var REQUIRE = RT.var("clojure.core", "require"); + + public ClojureBuilderImpl(PlasticProxyFactory proxyFactory, MethodToFunctionSymbolMapper mapper, OperationTracker tracker) + { + this.proxyFactory = proxyFactory; + this.mapper = mapper; + this.tracker = tracker; + } + + @Override + public <T> T build(final Class<T> interfaceType) + { + assert interfaceType != null; + assert interfaceType.isInterface(); + + Namespace annotation = interfaceType.getAnnotation(Namespace.class); + + if (annotation == null) + { + throw new IllegalArgumentException(String.format("Interface type %s does not have the Namespace annotation.", + interfaceType.getName())); + } + + final String namespace = annotation.value(); + + ClassInstantiator<T> instantiator = proxyFactory.createProxy(interfaceType, new PlasticClassTransformer() + { + @Override + public void transform(PlasticClass plasticClass) + { + for (final Method m : interfaceType.getMethods()) + { + bridgeToClojure(plasticClass, m); + } + } + + private void bridgeToClojure(final PlasticClass plasticClass, final Method method) + { + final MethodDescription desc = new MethodDescription(method); + + if (method.getReturnType() == void.class) + { + throw new IllegalArgumentException(String.format("Method %s may not be void when bridging to Clojure functions.", + desc)); + } + + final Symbol symbol = mapper.mapMethod(namespace, method); + + tracker.run(String.format("Mapping %s method %s to Clojure function %s", + interfaceType.getName(), + desc.toShortString(), + symbol.toString()), new Runnable() + { + @Override + public void run() + { + Symbol namespaceSymbol = Symbol.create(symbol.getNamespace()); + + REQUIRE.invoke(namespaceSymbol); + + Var var = Var.find(symbol); + + final PlasticField varField = plasticClass.introduceField(Var.class, method.getName() + "Var").inject(var); + + plasticClass.introduceMethod(desc).changeImplementation(new InstructionBuilderCallback() + { + @Override + public void doBuild(InstructionBuilder builder) + { + bridgeToClojure(builder, desc, varField); + } + }); + + } + }); + + } + + private void bridgeToClojure(InstructionBuilder builder, MethodDescription description, PlasticField varField) + { + builder.loadThis().getField(varField); + + int count = description.argumentTypes.length; + + Class[] invokeParameterTypes = new Class[count]; + + for (int i = 0; i < count; i++) + { + invokeParameterTypes[i] = Object.class; + + builder.loadArgument(i).boxPrimitive(description.argumentTypes[i]); + } + + Method ifnMethod = null; + + try + { + ifnMethod = IFn.class.getMethod("invoke", invokeParameterTypes); + } catch (NoSuchMethodException ex) + { + throw new RuntimeException(String.format("Unable to find correct IFn.invoke() method: %s", + InternalUtils.toMessage(ex)), ex); + } + + builder.invoke(ifnMethod); + + builder.castOrUnbox(description.returnType); + builder.returnResult(); + } + }); + + return instantiator.newInstance(); + } +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f3ab646c/tapestry-clojure/src/main/java/org/apache/tapestry5/internal/clojure/DefaultMapper.java ---------------------------------------------------------------------- diff --git a/tapestry-clojure/src/main/java/org/apache/tapestry5/internal/clojure/DefaultMapper.java b/tapestry-clojure/src/main/java/org/apache/tapestry5/internal/clojure/DefaultMapper.java new file mode 100644 index 0000000..cfdf0cf --- /dev/null +++ b/tapestry-clojure/src/main/java/org/apache/tapestry5/internal/clojure/DefaultMapper.java @@ -0,0 +1,77 @@ +// Copyright 2012 The Apache Software Foundation +// +// 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.apache.tapestry5.internal.clojure; + +import clojure.lang.Symbol; +import org.apache.tapestry5.clojure.MethodToFunctionSymbolMapper; + +import java.lang.reflect.Method; +import java.util.regex.MatchResult; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Default implementation that transforms a camelCased method-name to a clojure-style function name. + */ +public class DefaultMapper implements MethodToFunctionSymbolMapper +{ + @Override + public Symbol mapMethod(String namespace, Method method) + { + return mapMethodName(namespace, method.getName()); + } + + private Symbol mapMethodName(String namespace, String name) + { + return Symbol.create(namespace, transformName(name)); + } + + private final Pattern transition = Pattern.compile("(\\p{Lower}\\p{Upper})"); + + private String transformName(String name) + { + + Matcher matcher = transition.matcher(name); + + StringBuilder builder = new StringBuilder(); + + int lastx = 0; + + while (matcher.find()) + { + MatchResult matchResult = matcher.toMatchResult(); + + int start = matchResult.start(); + int end = matchResult.end(); + + builder.append(name.substring(lastx, start + 1)); + builder.append("-"); + // TODO: An acronym (such as "URL") should not be lower cased here. + builder.append(name.substring(end - 1, end).toLowerCase()); + + lastx = end; + } + + if (lastx == 0) + { + return name; + } + + builder.append(name.substring(lastx)); + + return builder.toString(); + } + +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f3ab646c/tapestry-clojure/src/test/groovy/org/apache/tapestry5/clojure/tests/AnnotationMapperSpec.groovy ---------------------------------------------------------------------- diff --git a/tapestry-clojure/src/test/groovy/org/apache/tapestry5/clojure/tests/AnnotationMapperSpec.groovy b/tapestry-clojure/src/test/groovy/org/apache/tapestry5/clojure/tests/AnnotationMapperSpec.groovy new file mode 100644 index 0000000..b70946a --- /dev/null +++ b/tapestry-clojure/src/test/groovy/org/apache/tapestry5/clojure/tests/AnnotationMapperSpec.groovy @@ -0,0 +1,57 @@ +package org.apache.tapestry5.clojure.tests + +import org.apache.tapestry5.clojure.FunctionName +import org.apache.tapestry5.internal.clojure.AnnotationMapper +import spock.lang.Specification +import spock.lang.Unroll + +interface MyInterface { + + Object alpha() + + @FunctionName("beta-func") + Object beta() + + @FunctionName("other/gamma-func") + Object gamma() +} + +class AnnotationMapperSpec extends Specification { + + def mapper = new AnnotationMapper() + + @Unroll + def "Symbol for method #methodName should be #namespace / #name"() { + + + def method = MyInterface.methods.find { it.name == methodName } + + when: + + def symbol = mapper.mapMethod("user", method) + + then: + + symbol.namespace == namespace + symbol.name == name + + where: + + methodName | namespace | name + "beta" | "user" | "beta-func" + "gamma" | "other" | "gamma-func" + } + + def "returns null if no annotation to provide a specific function name"() { + + when: + + def method = MyInterface.methods.find() { it.name == "alpha" } + + then: + + mapper.mapMethod("user", method) == null + + } + +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f3ab646c/tapestry-clojure/src/test/groovy/org/apache/tapestry5/clojure/tests/ClojureBuilderSpec.groovy ---------------------------------------------------------------------- diff --git a/tapestry-clojure/src/test/groovy/org/apache/tapestry5/clojure/tests/ClojureBuilderSpec.groovy b/tapestry-clojure/src/test/groovy/org/apache/tapestry5/clojure/tests/ClojureBuilderSpec.groovy new file mode 100644 index 0000000..6d2651d --- /dev/null +++ b/tapestry-clojure/src/test/groovy/org/apache/tapestry5/clojure/tests/ClojureBuilderSpec.groovy @@ -0,0 +1,43 @@ +package org.apache.tapestry5.clojure.tests + +import org.apache.tapestry5.clojure.ClojureModule +import org.apache.tapestry5.ioc.Registry +import org.apache.tapestry5.ioc.RegistryBuilder +import spock.lang.AutoCleanup +import spock.lang.Shared +import spock.lang.Specification + +/** + * Integration test for {@link org.apache.tapestry5.clojure.ClojureBuilder} service. + */ +class ClojureBuilderSpec extends Specification { + + @Shared @AutoCleanup("shutdown") + Registry registry; + + @Shared + Fixture fixture; + + def setupSpec() { + + RegistryBuilder builder = new RegistryBuilder().add(TestModule, ClojureModule) + + registry = builder.build(); + + registry.performRegistryStartup(); + + fixture = registry.getService(Fixture) + } + + def "invoke a method within the namespace"() { + expect: + + fixture.doubler(80) == 160 + } + + def "invoke a method mapped to a @FunctionName identified function"() { + expect: + + fixture.first(["fred", "barney"]) == "fred" + } +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f3ab646c/tapestry-clojure/src/test/groovy/org/apache/tapestry5/clojure/tests/DefaultFunctionNameMapperSpec.groovy ---------------------------------------------------------------------- diff --git a/tapestry-clojure/src/test/groovy/org/apache/tapestry5/clojure/tests/DefaultFunctionNameMapperSpec.groovy b/tapestry-clojure/src/test/groovy/org/apache/tapestry5/clojure/tests/DefaultFunctionNameMapperSpec.groovy new file mode 100644 index 0000000..e987b8d --- /dev/null +++ b/tapestry-clojure/src/test/groovy/org/apache/tapestry5/clojure/tests/DefaultFunctionNameMapperSpec.groovy @@ -0,0 +1,39 @@ +package org.apache.tapestry5.clojure.tests + +import org.apache.tapestry5.internal.clojure.DefaultMapper +import spock.lang.Specification +import spock.lang.Unroll + +class DefaultFunctionNameMapperSpec extends Specification { + + DefaultMapper mapper = new DefaultMapper() + + @Unroll + def "method name '#methodName' should map to function name '#functionName'"() { + + expect: + + mapper.transformName(methodName) == functionName + + where: + + methodName | functionName + "simple" | "simple" + "caseChangePoint" | "case-change-point" + "toASCII" | "to-aSCII" // Questionable! + } + + def "default conversion right from method"() { + + def method = Object.methods.find { it.name == "equals" } + + when: + + def symbol = mapper.mapMethod("user", method) + + then: + + symbol.namespace == "user" + symbol.name == "equals" + } +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f3ab646c/tapestry-clojure/src/test/java/org/apache/tapestry5/clojure/tests/Fixture.java ---------------------------------------------------------------------- diff --git a/tapestry-clojure/src/test/java/org/apache/tapestry5/clojure/tests/Fixture.java b/tapestry-clojure/src/test/java/org/apache/tapestry5/clojure/tests/Fixture.java new file mode 100644 index 0000000..a27229e --- /dev/null +++ b/tapestry-clojure/src/test/java/org/apache/tapestry5/clojure/tests/Fixture.java @@ -0,0 +1,15 @@ +package org.apache.tapestry5.clojure.tests; + +import org.apache.tapestry5.clojure.FunctionName; +import org.apache.tapestry5.clojure.Namespace; + +import java.util.List; + +@Namespace("fixture") +public interface Fixture +{ + long doubler(long value); + + @FunctionName("clojure.core/first") + Object first(List<?> list); +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f3ab646c/tapestry-clojure/src/test/java/org/apache/tapestry5/clojure/tests/TestModule.java ---------------------------------------------------------------------- diff --git a/tapestry-clojure/src/test/java/org/apache/tapestry5/clojure/tests/TestModule.java b/tapestry-clojure/src/test/java/org/apache/tapestry5/clojure/tests/TestModule.java new file mode 100644 index 0000000..2697ddc --- /dev/null +++ b/tapestry-clojure/src/test/java/org/apache/tapestry5/clojure/tests/TestModule.java @@ -0,0 +1,11 @@ +package org.apache.tapestry5.clojure.tests; + +import org.apache.tapestry5.clojure.ClojureBuilder; + +public class TestModule +{ + public static Fixture buildFixture(ClojureBuilder builder) + { + return builder.build(Fixture.class); + } +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f3ab646c/tapestry-clojure/src/test/resources/fixture.clj ---------------------------------------------------------------------- diff --git a/tapestry-clojure/src/test/resources/fixture.clj b/tapestry-clojure/src/test/resources/fixture.clj new file mode 100644 index 0000000..af34804 --- /dev/null +++ b/tapestry-clojure/src/test/resources/fixture.clj @@ -0,0 +1,6 @@ +(ns fixture + "Fixture for testing Tapestry/Clojure integration") + +(defn doubler + [i] + (* 2 i))
