pax_global_header00006660000000000000000000000064126457417200014522gustar00rootroot0000000000000052 comment=8b05f6e38747d98084b1479ecd5bde4432a82644 invokebinder-invokebinder-1.7/000077500000000000000000000000001264574172000165255ustar00rootroot00000000000000invokebinder-invokebinder-1.7/.gitignore000066400000000000000000000000271264574172000205140ustar00rootroot00000000000000/target/ /.idea/ *.iml invokebinder-invokebinder-1.7/.travis.yml000066400000000000000000000000171264574172000206340ustar00rootroot00000000000000language: java invokebinder-invokebinder-1.7/LICENSE000066400000000000000000000261361264574172000175420ustar00rootroot00000000000000 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. invokebinder-invokebinder-1.7/README.markdown000066400000000000000000000020021264574172000212200ustar00rootroot00000000000000Introduction ============ This library hopes to provide a more friendly DSL for binding method handles. Unlike the normal MethodHandle API, handles are bound forward from a source MethodType and eventually adapted to a final target MethodHandle. Along the way the transformations are pushed onto a stack and eventually applied in reverse order, as the standard API demands. Examples ======== Transformation calls can be chained. They are not applied until an eventual "invoke" is called with the target endpoint MethodHandle. MethodHandle mh = Binder .from(String.class, String.class, String.class) // String w(String, String) .drop(1, String.class) // String x(String) .insert(0, 'hello') // String y(String, String) .cast(String.class, CharSequence.class, Object.class) // String z(CharSequence, Object) .invoke(someTargetHandle); Status ====== This is currently under development. Not all transformations from the MethodHandle API are yet supported. Contributors are welcome :)invokebinder-invokebinder-1.7/nb-configuration.xml000066400000000000000000000017761264574172000225260ustar00rootroot00000000000000 JDK_1.7 invokebinder-invokebinder-1.7/nbactions-release-profile.xml000066400000000000000000000035031264574172000243040ustar00rootroot00000000000000 run process-classes org.codehaus.mojo:exec-maven-plugin:1.2:exec runtime -XX:+UnlockExperimentalVMOptions -XX:+EnableMethodHandles -classpath %classpath ${packageClassName} java debug process-classes org.codehaus.mojo:exec-maven-plugin:1.2:exec runtime -Xdebug -Xrunjdwp:transport=dt_socket,server=n,address=${jpda.address} -XX:+UnlockExperimentalVMOptions -XX:+EnableMethodHandles -classpath %classpath ${packageClassName} true java profile process-classes org.codehaus.mojo:exec-maven-plugin:1.1.1:exec ${profiler.args} -XX:+UnlockExperimentalVMOptions -XX:+EnableMethodHandles -classpath %classpath ${packageClassName} profile ${profiler.java} invokebinder-invokebinder-1.7/nbactions.xml000066400000000000000000000042051264574172000212300ustar00rootroot00000000000000 run process-classes org.codehaus.mojo:exec-maven-plugin:1.2:exec runtime -XX:+UnlockExperimentalVMOptions -XX:+EnableInvokeDynamic -classpath %classpath ${packageClassName} java debug process-classes org.codehaus.mojo:exec-maven-plugin:1.2:exec runtime -Xdebug -Xrunjdwp:transport=dt_socket,server=n,address=${jpda.address} -XX:+UnlockExperimentalVMOptions -XX:+EnableInvokeDynamic -classpath %classpath ${packageClassName} true java profile process-classes org.codehaus.mojo:exec-maven-plugin:1.1.1:exec ${profiler.args} -XX:+UnlockExperimentalVMOptions -XX:+EnableInvokeDynamic -classpath %classpath ${packageClassName} profile ${profiler.java} test test "-XX:+UnlockExperimentalVMOptions -XX:+EnableMethodHandles" invokebinder-invokebinder-1.7/pom.xml000066400000000000000000000056731264574172000200550ustar00rootroot00000000000000 4.0.0 com.headius invokebinder bundle 1.7 invokebinder http://maven.apache.org UTF-8 org.sonatype.oss oss-parent 7 The Apache Software License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt repo scm:git:https://github.com/headius/invokebinder.git scm:git:git@github.com:headius/invokebinder.git https://github.com/headius/invokebinder headius Charles Nutter headius@headius.com org.apache.felix maven-bundle-plugin true com.headius.invokebinder com.headius.invokebinder.* org.apache.maven.plugins maven-compiler-plugin 2.0.2 1.7 1.7 ${project.build.sourceEncoding} org.apache.maven.plugins maven-resources-plugin 2.2 ${project.build.sourceEncoding} junit junit 4.9 test invokebinder-invokebinder-1.7/src/000077500000000000000000000000001264574172000173145ustar00rootroot00000000000000invokebinder-invokebinder-1.7/src/main/000077500000000000000000000000001264574172000202405ustar00rootroot00000000000000invokebinder-invokebinder-1.7/src/main/java/000077500000000000000000000000001264574172000211615ustar00rootroot00000000000000invokebinder-invokebinder-1.7/src/main/java/com/000077500000000000000000000000001264574172000217375ustar00rootroot00000000000000invokebinder-invokebinder-1.7/src/main/java/com/headius/000077500000000000000000000000001264574172000233615ustar00rootroot00000000000000invokebinder-invokebinder-1.7/src/main/java/com/headius/invokebinder/000077500000000000000000000000001264574172000260405ustar00rootroot00000000000000invokebinder-invokebinder-1.7/src/main/java/com/headius/invokebinder/Binder.java000066400000000000000000001622241264574172000301150ustar00rootroot00000000000000/* * Copyright 2012-2014 headius. * * 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 com.headius.invokebinder; import com.headius.invokebinder.transform.Cast; import com.headius.invokebinder.transform.Catch; import com.headius.invokebinder.transform.Collect; import com.headius.invokebinder.transform.Convert; import com.headius.invokebinder.transform.Drop; import com.headius.invokebinder.transform.Filter; import com.headius.invokebinder.transform.FilterReturn; import com.headius.invokebinder.transform.Fold; import com.headius.invokebinder.transform.Insert; import com.headius.invokebinder.transform.Permute; import com.headius.invokebinder.transform.Spread; import com.headius.invokebinder.transform.Transform; import com.headius.invokebinder.transform.TryFinally; import com.headius.invokebinder.transform.Varargs; import java.io.PrintStream; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.ListIterator; import java.util.logging.Logger; /** * The Binder class provides a DSL for building a chain of MethodHandles using * various of the adaptations provided by java.lang.invoke.MethodHandles. The * transformations are pushed into a stack, allowing the DSL to operate forward * from an incoming signature rather than backward from a target handle. This * is often conceptually easier to understand, and certainly easier to read. * * The transformations are also applied simultaneously to the starting * java.lang.invoke.MethodType, allowing Binder to check at each step whether * the adaptation is valid. * * Here's a typical use, starting with a signature that takes two Strings and * returns a String, dropping and inserting arguments, casting to a target * signature, and finally calling a target handle with that signature. * *
 * MethodHandle mh = Binder
 *     .from(String.class, String.class, String.class) // String w(String, String)
 *     .drop(1, String.class) // String x(String)
 *     .insert(0, 'hello') // String y(String, String)
 *     .cast(String.class, CharSequence.class, Object.class) // String z(CharSequence, Object)String
 *     .invoke(someTargetHandle);
 * 
*/ public class Binder { private final Logger logger = Logger.getLogger("Invoke Binder"); private final List transforms = new ArrayList<>(); private final List types = new ArrayList<>(); private final MethodType start; private final MethodHandles.Lookup lookup; /** * Construct a new Binder, starting from a given MethodType. Use a public * java.lang.invoke.MethodHandles.Lookup for retrieving direct handles. * * @param start the starting MethodType, for calls entering the eventual chain */ public Binder(MethodType start) { this.start = start; this.types.add(0, start); this.lookup = MethodHandles.publicLookup(); } /** * Construct a new Binder, starting from a given Lookup and MethodType. * * @param lookup the Lookup context to use for direct handles * @param start the starting MethodType, for calls entering the eventual chain */ public Binder(MethodHandles.Lookup lookup, MethodType start) { this.start = start; this.types.add(0, start); this.lookup = lookup; } /** * Construct a new Binder using the given invokebinder. * * @param source a Binder to duplicate */ public Binder(Binder source) { this.start = source.start; this.types.addAll(source.types); this.transforms.addAll(source.transforms); this.lookup = source.lookup; } /** * Construct a new Binder using the given Lookup and invokebinder. * * @param lookup the Lookup context to use for direct handles * @param source the source Binder */ public Binder(MethodHandles.Lookup lookup, Binder source) { this.start = source.start; this.types.addAll(source.types); this.transforms.addAll(source.transforms); this.lookup = lookup; } /** * Construct a new Binder using the given invokebinder plus an additional transform * * @param source the source Binder * @param transform the additional Transform */ public Binder(Binder source, Transform transform) { this.start = source.start; this.types.addAll(source.types); this.transforms.addAll(source.transforms); add(transform); this.lookup = source.lookup; } /** * Construct a new Binder using the given invokebinder plus an additional transform and current type * * @param source the source Binder * @param transform the additional Transform * @param type the new current type resulting from the transform */ public Binder(Binder source, Transform transform, MethodType type) { this.start = source.start; this.types.addAll(source.types); this.transforms.addAll(source.transforms); add(transform, type); this.lookup = source.lookup; } /** * Construct a new Binder, starting from a given MethodType. * * @param start the starting MethodType, for calls entering the eventual chain * @return the Binder object */ public static Binder from(MethodType start) { return new Binder(start); } /** * Construct a new Binder, starting from a given MethodType. * * @param lookup the Lookup context to use for direct handles * @param start the starting MethodType, for calls entering the eventual chain * @return the Binder object */ public static Binder from(MethodHandles.Lookup lookup, MethodType start) { return new Binder(lookup, start); } /** * Construct a new Binder using a return type. * * @param returnType the return type of the incoming signature * @return the Binder object */ public static Binder from(Class returnType) { return from(MethodType.methodType(returnType)); } /** * Construct a new Binder using a return type. * * @param lookup the Lookup context to use for direct handles * @param returnType the return type of the incoming signature * @return the Binder object */ public static Binder from(MethodHandles.Lookup lookup, Class returnType) { return from(lookup, MethodType.methodType(returnType)); } /** * Construct a new Binder using a return type and argument types. * * @param returnType the return type of the incoming signature * @param argTypes the argument types of the incoming signature * @return the Binder object */ public static Binder from(Class returnType, Class[] argTypes) { return from(MethodType.methodType(returnType, argTypes)); } /** * Construct a new Binder using a return type and argument types. * * @param lookup the Lookup context to use for direct handles * @param returnType the return type of the incoming signature * @param argTypes the argument types of the incoming signature * @return the Binder object */ public static Binder from(MethodHandles.Lookup lookup, Class returnType, Class[] argTypes) { return from(lookup, MethodType.methodType(returnType, argTypes)); } /** * Construct a new Binder using a return type and argument types. * * @param returnType the return type of the incoming signature * @param argType0 the first argument type of the incoming signature * @param argTypes the remaining argument types of the incoming signature * @return the Binder object */ public static Binder from(Class returnType, Class argType0, Class... argTypes) { return from(MethodType.methodType(returnType, argType0, argTypes)); } /** * Construct a new Binder using a return type and argument types. * * @param lookup the Lookup context to use for direct handles * @param returnType the return type of the incoming signature * @param argType0 the first argument type of the incoming signature * @param argTypes the remaining argument types of the incoming signature * @return the Binder object */ public static Binder from(MethodHandles.Lookup lookup, Class returnType, Class argType0, Class... argTypes) { return from(lookup, MethodType.methodType(returnType, argType0, argTypes)); } /** * Construct a new Binder, starting from a given invokebinder. * * @param start the starting invokebinder; the new one will start with the current endpoint type * of the given invokebinder * @return the Binder object */ public static Binder from(Binder start) { return new Binder(start); } /** * Construct a new Binder, starting from a given invokebinder. * * @param lookup the Lookup context to use for direct handles * @param start the starting invokebinder; the new one will start with the current endpoint type * of the given invokebinder * @return the Binder object */ public static Binder from(MethodHandles.Lookup lookup, Binder start) { return new Binder(lookup, start); } /** * Join this binder to an existing one by applying its transformations after * this one. * * @param other the Binder containing the set of transformations to append * @return a new Binder combining this Binder with the target Binder */ public Binder to(Binder other) { assert type().equals(other.start); Binder newBinder = new Binder(this); for (ListIterator iter = other.transforms.listIterator(other.transforms.size()); iter.hasPrevious(); ) { Transform t = iter.previous(); newBinder.add(t); } return newBinder; } /** * Use an alternate java.lang.invoke.MethodHandles.Lookup as the default for * any direct handles created. * * @param lookup the new Lookup context to use * @return a new Binder with the given lookup */ public Binder withLookup(MethodHandles.Lookup lookup) { return from(lookup, this); } /** * Add a Transform to the chain. * * @param transform */ private void add(Transform transform) { add(transform, transform.down(types.get(0))); } /** * Add a Transform with an associated MethodType target to the chain. * * @param transform * @param target */ private void add(Transform transform, MethodType target) { types.add(0, target); transforms.add(0, transform); } /** * The current MethodType, were the handle chain to terminate at this point. * * @return the current MethodType */ public MethodType type() { return types.get(0); } /** * Println the current MethodType to the given stream. * * @param ps a PrintStream to which to println the current MethodType * @return this Binding */ public Binder printType(PrintStream ps) { ps.println(types.get(0)); return this; } /** * Println the current MethodType to stdout. * * @return this Binding */ public Binder printType() { return printType(System.out); } /** * Log the current MethodType as info. * * @return this Binding */ public Binder logType() { logger.info(types.get(0).toString()); return this; } /** * Insert at the given index the given boolean value. * * @param index the index at which to insert the argument value * @param value the value to insert * @return a new Binder */ public Binder insert(int index, boolean value) { return new Binder(this, new Insert(index, value)); } /** * Insert at the given index the given byte value. * * @param index the index at which to insert the argument value * @param value the value to insert * @return a new Binder */ public Binder insert(int index, byte value) { return new Binder(this, new Insert(index, value)); } /** * Insert at the given index the given short value. * * @param index the index at which to insert the argument value * @param value the value to insert * @return a new Binder */ public Binder insert(int index, short value) { return new Binder(this, new Insert(index, value)); } /** * Insert at the given index the given char value. * * @param index the index at which to insert the argument value * @param value the value to insert * @return a new Binder */ public Binder insert(int index, char value) { return new Binder(this, new Insert(index, value)); } /** * Insert at the given index the given int value. * * @param index the index at which to insert the argument value * @param value the value to insert * @return a new Binder */ public Binder insert(int index, int value) { return new Binder(this, new Insert(index, value)); } /** * Insert at the given index the given long value. * * @param index the index at which to insert the argument value * @param value the value to insert * @return a new Binder */ public Binder insert(int index, long value) { return new Binder(this, new Insert(index, value)); } /** * Insert at the given index the given float value. * * @param index the index at which to insert the argument value * @param value the value to insert * @return a new Binder */ public Binder insert(int index, float value) { return new Binder(this, new Insert(index, value)); } /** * Insert at the given index the given double value. * * @param index the index at which to insert the argument value * @param value the value to insert * @return a new Binder */ public Binder insert(int index, double value) { return new Binder(this, new Insert(index, value)); } /** * Insert at the given index the given argument value(s). * * @param index the index at which to insert the argument value * @param values the value(s) to insert * @return a new Binder */ public Binder insert(int index, Object... values) { return new Binder(this, new Insert(index, values)); } /** * Insert at the given index the given argument value. * * @param index the index at which to insert the argument value * @param type the actual type to use, rather than getClass * @param value the value to insert * @return a new Binder */ public Binder insert(int index, Class type, Object value) { return new Binder(this, new Insert(index, new Class[]{type}, value)); } /** * Insert at the given index the given argument value(s). * * @param index the index at which to insert the argument value * @param types the actual types to use, rather than getClass * @param values the value(s) to insert * @return a new Binder */ public Binder insert(int index, Class[] types, Object... values) { return new Binder(this, new Insert(index, types, values)); } /** * Append to the argument list the given boolean value. * * @param value the value to append * @return a new Binder */ public Binder append(boolean value) { return new Binder(this, new Insert(type().parameterCount(), value)); } /** * Append to the argument list the given byte value. * * @param value the value to append * @return a new Binder */ public Binder append(byte value) { return new Binder(this, new Insert(type().parameterCount(), value)); } /** * Append to the argument list the given short value. * * @param value the value to append * @return a new Binder */ public Binder append(short value) { return new Binder(this, new Insert(type().parameterCount(), value)); } /** * Append to the argument list the given char value. * * @param value the value to append * @return a new Binder */ public Binder append(char value) { return new Binder(this, new Insert(type().parameterCount(), value)); } /** * Append to the argument list the given int value. * * @param value the value to append * @return a new Binder */ public Binder append(int value) { return new Binder(this, new Insert(type().parameterCount(), value)); } /** * Append to the argument list the given long value. * * @param value the value to append * @return a new Binder */ public Binder append(long value) { return new Binder(this, new Insert(type().parameterCount(), value)); } /** * Append to the argument list the given float value. * * @param value the value to append * @return a new Binder */ public Binder append(float value) { return new Binder(this, new Insert(type().parameterCount(), value)); } /** * Append to the argument list the given double value. * * @param value the value to append * @return a new Binder */ public Binder append(double value) { return new Binder(this, new Insert(type().parameterCount(), value)); } /** * Append to the argument list the given argument value(s). * * @param values the value(s) to append * @return a new Binder */ public Binder append(Object... values) { return new Binder(this, new Insert(type().parameterCount(), values)); } /** * Prepend to the argument list the given boolean value. * * @param value the value to prepend * @return a new Binder */ public Binder prepend(boolean value) { return new Binder(this, new Insert(0, value)); } /** * Prepend to the argument list the given byte value. * * @param value the value to prepend * @return a new Binder */ public Binder prepend(byte value) { return new Binder(this, new Insert(0, value)); } /** * Prepend to the argument list the given short value. * * @param value the value to prepend * @return a new Binder */ public Binder prepend(short value) { return new Binder(this, new Insert(0, value)); } /** * Prepend to the argument list the given char value. * * @param value the value to prepend * @return a new Binder */ public Binder prepend(char value) { return new Binder(this, new Insert(0, value)); } /** * Prepend to the argument list the given int value. * * @param value the value to prepend * @return a new Binder */ public Binder prepend(int value) { return new Binder(this, new Insert(0, value)); } /** * Prepend to the argument list the given long value. * * @param value the value to prepend * @return a new Binder */ public Binder prepend(long value) { return new Binder(this, new Insert(0, value)); } /** * Prepend to the argument list the given float value. * * @param value the value to prepend * @return a new Binder */ public Binder prepend(float value) { return new Binder(this, new Insert(0, value)); } /** * Prepend to the argument list the given double value. * * @param value the value to prepend * @return a new Binder */ public Binder prepend(double value) { return new Binder(this, new Insert(0, value)); } /** * Prepend to the argument list the given argument value(s). * * @param values the value(s) to prepend * @return a new Binder */ public Binder prepend(Object... values) { return new Binder(this, new Insert(0, values)); } /** * Append to the argument list the given argument value with the specified type. * * @param type the actual type to use, rather than getClass * @param value the value to append * @return a new Binder */ public Binder append(Class type, Object value) { return new Binder(this, new Insert(type().parameterCount(), new Class[]{type}, value)); } /** * Append to the argument list the given argument values with the specified types. * * @param types the actual types to use, rather than getClass * @param values the value(s) to append * @return a new Binder */ public Binder append(Class[] types, Object... values) { return new Binder(this, new Insert(type().parameterCount(), types, values)); } /** * Prepend to the argument list the given argument value with the specified type * * @param type the actual type to use, rather than getClass * @param value the value(s) to prepend * @return a new Binder */ public Binder prepend(Class type, Object value) { return new Binder(this, new Insert(0, new Class[]{type}, value)); } /** * Prepend to the argument list the given argument values with the specified types. * * @param types the actual types to use, rather than getClass * @param values the value(s) to prepend * @return a new Binder */ public Binder prepend(Class[] types, Object... values) { return new Binder(this, new Insert(0, types, values)); } /** * Drop a single argument at the given index. * * @param index the index at which to drop an argument * @return a new Binder */ public Binder drop(int index) { return drop(index, 1); } /** * Drop from the given index a number of arguments. * * @param index the index at which to start dropping * @param count the number of arguments to drop * @return a new Binder */ public Binder drop(int index, int count) { return new Binder(this, new Drop(index, Arrays.copyOfRange(type().parameterArray(), index, index + count))); } /** * Drop a single argument at the end of the argument list. * * @return a new Binder */ public Binder dropLast() { return dropLast(1); } /** * Drop from the end of the argument list a number of arguments. * * @param count the number of arguments to drop * @return a new Binder */ public Binder dropLast(int count) { assert count <= type().parameterCount(); return drop(type().parameterCount() - count, count); } /** * Drop a single argument at the beginning of the argument list. * * @return a new Binder */ public Binder dropFirst() { return dropFirst(1); } /** * Drop from the end of the argument list a number of arguments. * * @param count the number of arguments to drop * @return a new Binder */ public Binder dropFirst(int count) { assert count <= type().parameterCount(); return drop(0, count); } /** * Drop all arguments from this handle chain * * @return a new Binder */ public Binder dropAll() { return drop(0, type().parameterCount()); } /** * Convert the incoming arguments to the given MethodType. The conversions * applied are equivalent to those in MethodHandle.asType(MethodType). * * @param target the target MethodType * @return a new Binder */ public Binder convert(MethodType target) { return new Binder(this, new Convert(type()), target); } /** * Convert the incoming arguments to the given MethodType. The conversions * applied are equivalent to those in MethodHandle.asType(MethodType). * * @param returnType the target return type * @param argTypes the target argument types * @return a new Binder */ public Binder convert(Class returnType, Class... argTypes) { return new Binder(this, new Convert(type()), MethodType.methodType(returnType, argTypes)); } /** * Cast the incoming arguments to the given MethodType. The casts * applied are equivalent to those in MethodHandles.explicitCastArguments(mh, MethodType). * * @param type the target MethodType * @return a new Binder */ public Binder cast(MethodType type) { return new Binder(this, new Cast(type()), type); } /** * Cast the incoming arguments to the given MethodType. The casts * applied are equivalent to those in MethodHandle.explicitCastArguments(MethodType). * * @param returnType the target return type * @param argTypes the target argument types * @return a new Binder */ public Binder cast(Class returnType, Class... argTypes) { return new Binder(this, new Cast(type()), MethodType.methodType(returnType, argTypes)); } /** * Cast the incoming arguments to the given MethodType. The casts * applied are equivalent to those in MethodHandle.explicitCastArguments(MethodType). * * @param returnType the target return type * @param firstType the first argument type, usually a target type * @param restTypes the remaining target argument types * @return a new Binder */ public Binder castVirtual(Class returnType, Class firstType, Class... restTypes) { return new Binder(this, new Cast(type()), MethodType.methodType(returnType, firstType, restTypes)); } /** * Spread a trailing array argument into the specified argument types. * * @param spreadTypes the types into which to spread the incoming Object[] * @return a new Binder */ public Binder spread(Class... spreadTypes) { if (spreadTypes.length == 0) { return dropLast(); } return new Binder(this, new Spread(type(), spreadTypes)); } /** * Spread a trailing array argument into the given number of arguments of * the type of the array. * * @param count the new count of arguments to spread from the trailing array * @return a new Binder */ public Binder spread(int count) { if (count == 0) { return dropLast(); } Class aryType = type().parameterType(type().parameterCount() - 1); assert aryType.isArray(); Class[] spreadTypes = new Class[count]; Arrays.fill(spreadTypes, aryType.getComponentType()); return spread(spreadTypes); } /** * Box all incoming arguments from the given position onward into the given array type. * * @param index the index from which to start boxing args * @param type the array type into which the args will be boxed * @return a new Binder */ public Binder collect(int index, Class type) { return new Binder(this, new Collect(type(), index, type)); } /** * Box a range of incoming arguments into the given array type. * * @param index the index from which to start boxing args * @param count the count of arguments to box * @param type the array type into which the args will be boxed * @return a new Binder */ public Binder collect(int index, int count, Class type) { return new Binder(this, new Collect(type(), index, count, type)); } /** * Box all incoming arguments from the given position onward into the given array type. * This version accepts a variable number of incoming arguments. * * @param index the index from which to start boxing args * @param type the array type into which the args will be boxed * @return a new Binder */ public Binder varargs(int index, Class type) { return new Binder(this, new Varargs(type(), index, type)); } /** * Permute the incoming arguments to a new sequence specified by the given values. * * Arguments may be duplicated or dropped in this sequence. * * @param reorder the int offsets of the incoming arguments in the desired permutation * @return a new Binder */ public Binder permute(int... reorder) { return new Binder(this, new Permute(type(), reorder)); } /** * Process the incoming arguments using the given handle, inserting the result * as the first argument. * * @param function the function that will process the incoming arguments. Its * signature must match the current signature's arguments exactly. * @return a new Binder */ public Binder fold(MethodHandle function) { return new Binder(this, new Fold(function)); } public Binder foldVoid(MethodHandle function) { if (type().returnType() == void.class) { return fold(function); } else { return fold(function.asType(function.type().changeReturnType(void.class))); } } /** * Process the incoming arguments by calling the given static method on the * given class, inserting the result as the first argument. * * @param lookup the java.lang.invoke.MethodHandles.Lookup to use * @param target the class on which the method is defined * @param method the method to invoke on the first argument * @return a new Binder */ public Binder foldStatic(MethodHandles.Lookup lookup, Class target, String method) { return fold(Binder.from(type()).invokeStaticQuiet(lookup, target, method)); } /** * Process the incoming arguments by calling the given static method on the * given class, inserting the result as the first argument. * * @param target the class on which the method is defined * @param method the method to invoke on the first argument * @return a new Binder */ public Binder foldStatic(Class target, String method) { return foldStatic(lookup, target, method); } /** * Process the incoming arguments by calling the given method on the first * argument, inserting the result as the first argument. * * @param lookup the java.lang.invoke.MethodHandles.Lookup to use * @param method the method to invoke on the first argument * @return a new Binder */ public Binder foldVirtual(MethodHandles.Lookup lookup, String method) { return fold(Binder.from(type()).invokeVirtualQuiet(lookup, method)); } /** * Process the incoming arguments by calling the given method on the first * argument, inserting the result as the first argument. * * @param method the method to invoke on the first argument * @return a new Binder */ public Binder foldVirtual(String method) { return foldVirtual(lookup, method); } /** * Filter incoming arguments, starting at the given index, replacing each with the * result of calling the associated function in the given list. * * @param index the index of the first argument to filter * @param functions the array of functions to transform the arguments * @return a new Binder */ public Binder filter(int index, MethodHandle... functions) { return new Binder(this, new Filter(index, functions)); } /** * Filter return value, using a function that produces the current return type * from another type. The new endpoint will have the return value that the * filter function accepts as an argument. * * @param function the array of functions to transform the arguments * @return a new Binder */ public Binder filterReturn(MethodHandle function) { return new Binder(this, new FilterReturn(function)); } /** * Apply transforms to run the given handle's logic as a "finally" block. * * try { * some_code // your eventual endpoint * } finally { * finally_logic // the given handle * } * * The layering uses a combination of catch and fold to reuse the same target * handle for both exceptional and non-exceptional paths. In essence, the * result is equivalent to using the given post logic as both an exception * handler (using catchException) and a "post fold" that runs after the main * downstream handles have run. * * @param post the logic that would live inside the "finally" block * @return a new Binder */ public Binder tryFinally(MethodHandle post) { return new Binder(this, new TryFinally(post)); } /** * Catch the given exception type from the downstream chain and handle it with the * given function. * * @param throwable the exception type to catch * @param function the function to use for handling the exception * @return a new Binder */ public Binder catchException(Class throwable, MethodHandle function) { return new Binder(this, new Catch(throwable, function)); } /** * Apply all transforms to an endpoint that does absolutely nothing. Useful for * creating exception handlers in void methods that simply ignore the exception. * * @return a handle that has all transforms applied and does nothing at its endpoint */ public MethodHandle nop() { if (type().returnType() != void.class) { throw new InvalidTransformException("must have void return type to nop: " + type()); } return invoke(Binder .from(type()) .drop(0, type().parameterCount()) .cast(Object.class) .constant(null)); } /** * Throw the current signature's sole Throwable argument. Return type * does not matter, since it will never return. * * @return a handle that has all transforms applied and which will eventually throw an exception */ public MethodHandle throwException() { if (type().parameterCount() != 1 || !Throwable.class.isAssignableFrom(type().parameterType(0))) { throw new InvalidTransformException("incoming signature must have one Throwable type as its sole argument: " + type()); } return invoke(MethodHandles.throwException(type().returnType(), type().parameterType(0).asSubclass(Throwable.class))); } /** * Apply the tranforms, binding them to a constant value that will * propagate back through the chain. The chain's expected return type * at that point must be compatible with the given value's type. * * @param value the constant value to put at the end of the chain * @return a handle that has all transforms applied in sequence up to the constant */ public MethodHandle constant(Object value) { return invoke(MethodHandles.constant(type().returnType(), value)); } /** * Apply the tranforms, binding them to a handle that will simply return its sole * argument as its return value. The endpoint signature must have a single argument * of the same type as its return type. * * @return a handle that has all transforms applied in sequence */ public MethodHandle identity() { return invoke(MethodHandles.identity(type().parameterType(0))); } /** * Apply the chain of transforms with the target method handle as the final * endpoint. Produces a handle that has the transforms in given sequence. * * If the final handle's type does not exactly match the initial type for * this Binder, an additional cast will be attempted. * * @param target the endpoint handle to bind to * @return a handle that has all transforms applied in sequence up to endpoint */ public MethodHandle invoke(MethodHandle target) { MethodHandle current = target; for (Transform t : transforms) { current = t.up(current); } // if resulting handle's type does not match start, attempt one more cast current = MethodHandles.explicitCastArguments(current, start); return current; } /** * Apply the chain of transforms and bind them to a static method specified * using the end signature plus the given class and method. The method will * be retrieved using the given Lookup and must match the end signature * exactly. * * If the final handle's type does not exactly match the initial type for * this Binder, an additional cast will be attempted. * * @param lookup the MethodHandles.Lookup to use to unreflect the method * @param method the Method to unreflect * @return the full handle chain, bound to the given method * @throws java.lang.IllegalAccessException if the method is not accessible */ public MethodHandle invoke(MethodHandles.Lookup lookup, Method method) throws IllegalAccessException { return invoke(lookup.unreflect(method)); } /** * Apply the chain of transforms and bind them to a static method specified * using the end signature plus the given class and method. The method will * be retrieved using the given Lookup and must match the end signature * exactly. * * If the final handle's type does not exactly match the initial type for * this Binder, an additional cast will be attempted. * * This version is "quiet" in that it throws an unchecked InvalidTransformException * if the target method does not exist or is inaccessible. * * @param lookup the MethodHandles.Lookup to use to unreflect the method * @param method the Method to unreflect * @return the full handle chain, bound to the given method */ public MethodHandle invokeQuiet(MethodHandles.Lookup lookup, Method method) { try { return invoke(lookup, method); } catch (IllegalAccessException iae) { throw new InvalidTransformException(iae); } } /** * Apply the chain of transforms and bind them to a static method specified * using the end signature plus the given class and name. The method will * be retrieved using the given Lookup and must match the end signature * exactly. * * If the final handle's type does not exactly match the initial type for * this Binder, an additional cast will be attempted. * * @param lookup the MethodHandles.Lookup to use to unreflect the method * @param target the class in which to find the method * @param name the name of the method to invoke * @return the full handle chain, bound to the given method * @throws java.lang.NoSuchMethodException if the method does not exist * @throws java.lang.IllegalAccessException if the method is not accessible */ public MethodHandle invokeStatic(MethodHandles.Lookup lookup, Class target, String name) throws NoSuchMethodException, IllegalAccessException { return invoke(lookup.findStatic(target, name, type())); } /** * Apply the chain of transforms and bind them to a static method specified * using the end signature plus the given class and name. The method will * be retrieved using the given Lookup and must match the end signature * exactly. * * If the final handle's type does not exactly match the initial type for * this Binder, an additional cast will be attempted. * * This version is "quiet" in that it throws an unchecked InvalidTransformException * if the target method does not exist or is inaccessible. * * @param lookup the MethodHandles.Lookup to use to look up the method * @param target the class in which to find the method * @param name the name of the method to invoke * @return the full handle chain, bound to the given method */ public MethodHandle invokeStaticQuiet(MethodHandles.Lookup lookup, Class target, String name) { try { return invokeStatic(lookup, target, name); } catch (IllegalAccessException | NoSuchMethodException e) { throw new InvalidTransformException(e); } } /** * Apply the chain of transforms and bind them to a virtual method specified * using the end signature plus the given class and name. The method will * be retrieved using the given Lookup and must match the end signature * exactly. * * If the final handle's type does not exactly match the initial type for * this Binder, an additional cast will be attempted. * * @param lookup the MethodHandles.Lookup to use to look up the method * @param name the name of the method to invoke * @return the full handle chain, bound to the given method * @throws java.lang.NoSuchMethodException if the method does not exist * @throws java.lang.IllegalAccessException if the method is not accessible */ public MethodHandle invokeVirtual(MethodHandles.Lookup lookup, String name) throws NoSuchMethodException, IllegalAccessException { return invoke(lookup.findVirtual(type().parameterType(0), name, type().dropParameterTypes(0, 1))); } /** * Apply the chain of transforms and bind them to a virtual method specified * using the end signature plus the given class and name. The method will * be retrieved using the given Lookup and must match the end signature * exactly. * * If the final handle's type does not exactly match the initial type for * this Binder, an additional cast will be attempted. * * This version is "quiet" in that it throws an unchecked InvalidTransformException * if the target method does not exist or is inaccessible. * * @param lookup the MethodHandles.Lookup to use to look up the method * @param name the name of the method to invoke * @return the full handle chain, bound to the given method */ public MethodHandle invokeVirtualQuiet(MethodHandles.Lookup lookup, String name) { try { return invokeVirtual(lookup, name); } catch (IllegalAccessException | NoSuchMethodException e) { throw new InvalidTransformException(e); } } /** * Apply the chain of transforms and bind them to a special method specified * using the end signature plus the given class and name. The method will * be retrieved using the given Lookup and must match the end signature * exactly. * * If the final handle's type does not exactly match the initial type for * this Binder, an additional cast will be attempted. * * @param lookup the MethodHandles.Lookup to use to look up the method * @param name the name of the method to invoke * @param caller the calling class * @return the full handle chain, bound to the given method * @throws java.lang.NoSuchMethodException if the method does not exist * @throws java.lang.IllegalAccessException if the method is not accessible */ public MethodHandle invokeSpecial(MethodHandles.Lookup lookup, String name, Class caller) throws NoSuchMethodException, IllegalAccessException { return invoke(lookup.findSpecial(type().parameterType(0), name, type().dropParameterTypes(0, 1), caller)); } /** * Apply the chain of transforms and bind them to a special method specified * using the end signature plus the given class and name. The method will * be retrieved using the given Lookup and must match the end signature * exactly. * * If the final handle's type does not exactly match the initial type for * this Binder, an additional cast will be attempted. * * This version is "quiet" in that it throws an unchecked InvalidTransformException * if the target method does not exist or is inaccessible. * * @param lookup the MethodHandles.Lookup to use to look up the method * @param name the name of the method to invoke * @param caller the calling class * @return the full handle chain, bound to the given method */ public MethodHandle invokeSpecialQuiet(MethodHandles.Lookup lookup, String name, Class caller) { try { return invokeSpecial(lookup, name, caller); } catch (IllegalAccessException | NoSuchMethodException e) { throw new InvalidTransformException(e); } } /** * Apply the chain of transforms and bind them to a constructor specified * using the end signature plus the given class. The constructor will * be retrieved using the given Lookup and must match the end signature's * arguments exactly. * * If the final handle's type does not exactly match the initial type for * this Binder, an additional cast will be attempted. * * @param lookup the MethodHandles.Lookup to use to look up the constructor * @param target the constructor's class * @return the full handle chain, bound to the given constructor * @throws java.lang.NoSuchMethodException if the constructor does not exist * @throws java.lang.IllegalAccessException if the constructor is not accessible */ public MethodHandle invokeConstructor(MethodHandles.Lookup lookup, Class target) throws NoSuchMethodException, IllegalAccessException { return invoke(lookup.findConstructor(target, type().changeReturnType(void.class))); } /** * Apply the chain of transforms and bind them to a constructor specified * using the end signature plus the given class. The constructor will * be retrieved using the given Lookup and must match the end signature's * arguments exactly. * * If the final handle's type does not exactly match the initial type for * this Binder, an additional cast will be attempted. * * This version is "quiet" in that it throws an unchecked InvalidTransformException * if the target method does not exist or is inaccessible. * * @param lookup the MethodHandles.Lookup to use to look up the constructor * @param target the constructor's class * @return the full handle chain, bound to the given constructor */ public MethodHandle invokeConstructorQuiet(MethodHandles.Lookup lookup, Class target) { try { return invokeConstructor(lookup, target); } catch (IllegalAccessException | NoSuchMethodException e) { throw new InvalidTransformException(e); } } /** * Apply the chain of transforms and bind them to an object field retrieval specified * using the end signature plus the given class and name. The field must * match the end signature's return value and the end signature must take * the target class or a subclass as its only argument. * * If the final handle's type does not exactly match the initial type for * this Binder, an additional cast will be attempted. * * @param lookup the MethodHandles.Lookup to use to look up the field * @param name the field's name * @return the full handle chain, bound to the given field access * @throws java.lang.NoSuchFieldException if the field does not exist * @throws java.lang.IllegalAccessException if the field is not accessible * @throws java.lang.NoSuchFieldException if the field does not exist * @throws java.lang.IllegalAccessException if the field is not accessible */ public MethodHandle getField(MethodHandles.Lookup lookup, String name) throws NoSuchFieldException, IllegalAccessException { return invoke(lookup.findGetter(type().parameterType(0), name, type().returnType())); } /** * Apply the chain of transforms and bind them to an object field retrieval specified * using the end signature plus the given class and name. The field must * match the end signature's return value and the end signature must take * the target class or a subclass as its only argument. * * If the final handle's type does not exactly match the initial type for * this Binder, an additional cast will be attempted. * * This version is "quiet" in that it throws an unchecked InvalidTransformException * if the target method does not exist or is inaccessible. * * @param lookup the MethodHandles.Lookup to use to look up the field * @param name the field's name * @return the full handle chain, bound to the given field access */ public MethodHandle getFieldQuiet(MethodHandles.Lookup lookup, String name) { try { return getField(lookup, name); } catch (IllegalAccessException | NoSuchFieldException e) { throw new InvalidTransformException(e); } } /** * Apply the chain of transforms and bind them to a static field retrieval specified * using the end signature plus the given class and name. The field must * match the end signature's return value and the end signature must take * no arguments. * * If the final handle's type does not exactly match the initial type for * this Binder, an additional cast will be attempted. * * @param lookup the MethodHandles.Lookup to use to look up the field * @param target the class in which the field is defined * @param name the field's name * @return the full handle chain, bound to the given field access * @throws java.lang.NoSuchFieldException if the field does not exist * @throws java.lang.IllegalAccessException if the field is not accessible or cannot be modified */ public MethodHandle getStatic(MethodHandles.Lookup lookup, Class target, String name) throws NoSuchFieldException, IllegalAccessException { return invoke(lookup.findStaticGetter(target, name, type().returnType())); } /** * Apply the chain of transforms and bind them to a static field retrieval specified * using the end signature plus the given class and name. The field must * match the end signature's return value and the end signature must take * no arguments. * * If the final handle's type does not exactly match the initial type for * this Binder, an additional cast will be attempted. * * This version is "quiet" in that it throws an unchecked InvalidTransformException * if the target method does not exist or is inaccessible. * * @param lookup the MethodHandles.Lookup to use to look up the field * @param target the class in which the field is defined * @param name the field's name * @return the full handle chain, bound to the given field access */ public MethodHandle getStaticQuiet(MethodHandles.Lookup lookup, Class target, String name) { try { return getStatic(lookup, target, name); } catch (IllegalAccessException | NoSuchFieldException e) { throw new InvalidTransformException(e); } } /** * Apply the chain of transforms and bind them to an object field assignment specified * using the end signature plus the given class and name. The end signature must take * the target class or a subclass and the field's type as its arguments, and its return * type must be compatible with void. * * If the final handle's type does not exactly match the initial type for * this Binder, an additional cast will be attempted. * * @param lookup the MethodHandles.Lookup to use to look up the field * @param name the field's name * @return the full handle chain, bound to the given field assignment * @throws java.lang.NoSuchFieldException if the field does not exist * @throws java.lang.IllegalAccessException if the field is not accessible or cannot be modified */ public MethodHandle setField(MethodHandles.Lookup lookup, String name) throws NoSuchFieldException, IllegalAccessException { return invoke(lookup.findSetter(type().parameterType(0), name, type().parameterType(1))); } /** * Apply the chain of transforms and bind them to an object field assignment specified * using the end signature plus the given class and name. The end signature must take * the target class or a subclass and the field's type as its arguments, and its return * type must be compatible with void. * * If the final handle's type does not exactly match the initial type for * this Binder, an additional cast will be attempted. * * This version is "quiet" in that it throws an unchecked InvalidTransformException * if the target method does not exist or is inaccessible. * * @param lookup the MethodHandles.Lookup to use to look up the field * @param name the field's name * @return the full handle chain, bound to the given field assignment */ public MethodHandle setFieldQuiet(MethodHandles.Lookup lookup, String name) { try { return setField(lookup, name); } catch (IllegalAccessException | NoSuchFieldException e) { throw new InvalidTransformException(e); } } /** * Apply the chain of transforms and bind them to an object field assignment specified * using the end signature plus the given class and name. The end signature must take * the target class or a subclass and the field's type as its arguments, and its return * type must be compatible with void. * * If the final handle's type does not exactly match the initial type for * this Binder, an additional cast will be attempted. * * @param lookup the MethodHandles.Lookup to use to look up the field * @param target the class in which the field is defined * @param name the field's name * @return the full handle chain, bound to the given field assignment * @throws java.lang.NoSuchFieldException if the field does not exist * @throws java.lang.IllegalAccessException if the field is not accessible or cannot be modified */ public MethodHandle setStatic(MethodHandles.Lookup lookup, Class target, String name) throws NoSuchFieldException, IllegalAccessException { return invoke(lookup.findStaticSetter(target, name, type().parameterType(0))); } /** * Apply the chain of transforms and bind them to an object field assignment specified * using the end signature plus the given class and name. The end signature must take * the target class or a subclass and the field's type as its arguments, and its return * type must be compatible with void. * * If the final handle's type does not exactly match the initial type for * this Binder, an additional cast will be attempted. * * This version is "quiet" in that it throws an unchecked InvalidTransformException * if the target method does not exist or is inaccessible. * * @param lookup the MethodHandles.Lookup to use to look up the field * @param target the class in which the field is defined * @param name the field's name * @return the full handle chain, bound to the given field assignment */ public MethodHandle setStaticQuiet(MethodHandles.Lookup lookup, Class target, String name) { try { return setStatic(lookup, target, name); } catch (IllegalAccessException | NoSuchFieldException e) { throw new InvalidTransformException(e); } } /** * Apply the chain of transforms and bind them to an array element set. The signature * at the endpoint must return void and receive the array type, int index, and array * element type. * * @return the full handle chain, bound to an array element set. */ public MethodHandle arraySet() { return invoke(MethodHandles.arrayElementSetter(type().parameterType(0))); } /** * Apply the chain of transforms and bind them to an array element get. The signature * at the endpoint must return the array element type and receive the array type and * int index. * * @return the full handle chain, bound to an array element get. */ public MethodHandle arrayGet() { return invoke(MethodHandles.arrayElementGetter(type().parameterType(0))); } /** * Apply the chain of transforms and bind them to a boolean branch as from * java.lang.invoke.MethodHandles.guardWithTest. As with GWT, the current endpoint * signature must match the given target and fallback signatures. * * @param test the test handle * @param truePath the target handle * @param falsePath the fallback handle * @return the full handle chain bound to a branch */ public MethodHandle branch(MethodHandle test, MethodHandle truePath, MethodHandle falsePath) { return invoke(MethodHandles.guardWithTest(test, truePath, falsePath)); } /** * Produce a MethodHandle that invokes its leading MethodHandle argument * with the remaining arguments, returning the result. * * @return a new handle that invokes its leading MethodHandle argument */ public MethodHandle invoker() { return invoke(MethodHandles.invoker(start)); } } invokebinder-invokebinder-1.7/src/main/java/com/headius/invokebinder/InvalidTransformException.java000066400000000000000000000016761264574172000340560ustar00rootroot00000000000000/* * Copyright 2012-2014 headius. * * 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 com.headius.invokebinder; /** * Exception raised when a Binder transform is not valid for the current * signature. */ public class InvalidTransformException extends RuntimeException { public InvalidTransformException(String message) { super(message); } public InvalidTransformException(Exception e) { super(e); } } invokebinder-invokebinder-1.7/src/main/java/com/headius/invokebinder/Signature.java000066400000000000000000000677371264574172000306700ustar00rootroot00000000000000/* * Copyright 2013-2014 headius. * * 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 com.headius.invokebinder; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Signature represents a series of method arguments plus their symbolic names. * * In order to make it easier to permute arguments, track their flow, and debug * cases where reordering or permuting fails to work properly, the Signature * class also tracks symbolic names for all arguments. This allows permuting * by name or by name pattern, avoiding the error-prone juggling of int[] for * the standard MethodHandles.permuteArguments call. * * A Signature is created starting using #thatReturns method, and expanded using * #withArgument for each named argument in sequence. Order is preserved. * * A Signature can be mutated into another by manipuating the argument list as * with java.lang.invoke.MethodType, but using argument names and name patterns * instead of integer offsets. * * Two signatures can be used to produce a permute array suitable for use in * java.lang.invoke.MethodHandles#permuteArguments using the #to methods. The * #to method can also accept a list of argument names, as a shortcut. * * @author headius */ public class Signature { private final MethodType methodType; private final String[] argNames; /** * Construct a new signature with the given return value. * * @param retval the return value for the new signature */ Signature(Class retval) { this(MethodType.methodType(retval)); } /** * Construct a new signature with the given return value, argument types, * and argument names. * * @param retval the return value for the new signature * @param argTypes the argument types for the new signature * @param argNames the argument names for the new signature */ Signature(Class retval, Class[] argTypes, String... argNames) { this(MethodType.methodType(retval, argTypes), argNames); } /** * Construct a new signature with the given return value, argument types, * and argument names. * * @param retval the return value for the new signature * @param firstArg the first argument type, often the receiver of an instance method * @param restArgs the remaining argument types for the new signature * @param argNames the argument names for the new signature */ Signature(Class retval, Class firstArg, Class[] restArgs, String... argNames) { this(MethodType.methodType(retval, firstArg, restArgs), argNames); } /** * Construct a new signature with the given method type and argument names. * * @param methodType the method type for the new signature * @param argNames the argument names for the new signature */ Signature(MethodType methodType, String... argNames) { assert methodType.parameterCount() == argNames.length : "arg name count " + argNames.length + " does not match parameter count " + methodType.parameterCount(); this.methodType = methodType; this.argNames = argNames; } /** * Construct a new signature with the given method type and argument names. * * @param methodType the method type for the new signature * @param firstName the first argument name for the new signature; for eventual instance methods, it can be "this" * @param restNames the remaining argument names for the new signature */ Signature(MethodType methodType, String firstName, String... restNames) { assert methodType.parameterCount() == (restNames.length + 1) : "arg name count " + (restNames.length + 1) + " does not match parameter count " + methodType.parameterCount(); this.methodType = methodType; this.argNames = new String[restNames.length + 1]; this.argNames[0] = firstName; System.arraycopy(restNames, 0, this.argNames, 1, restNames.length); } /** * Produce a human-readable representation of this signature. This * representation uses Class#getSimpleName to improve readability. * * @return a human-readable representation of the signature */ public String toString() { StringBuilder sb = new StringBuilder("("); for (int i = 0; i < argNames.length; i++) { sb.append(methodType.parameterType(i).getSimpleName()).append(' ').append(argNames[i]); if (i + 1 < argNames.length) { sb.append(", "); } } sb.append(")").append(methodType.returnType().getSimpleName()); return sb.toString(); } /** * Create a new signature returning the given type. * * @param retval the return type for the new signature * @return the new signature */ public static Signature returning(Class retval) { Signature sig = new Signature(retval); return sig; } /** * Create a new signature based on the given return value, argument types, and argument names * * @param retval the type of the return value * @param argTypes the types of the arguments * @param argNames the names of the arguments * @return a new Signature */ public static Signature from(Class retval, Class[] argTypes, String... argNames) { assert argTypes.length == argNames.length; return new Signature(retval, argTypes, argNames); } /** * Create a new signature based on this one with a different return type. * * @param retval the class for the new signature's return type * @return the new signature with modified return value */ public Signature changeReturn(Class retval) { return new Signature(methodType.changeReturnType(retval), argNames); } /** * Produce a new signature based on this one with a different return type. * * @param retval the new return type for the new signature * @return a new signature with the added argument */ public Signature asFold(Class retval) { return new Signature(methodType.changeReturnType(retval), argNames); } /** * Append an argument (name + type) to the signature. * * @param name the name of the argument * @param type the type of the argument * @return a new signature with the added arguments */ public Signature appendArg(String name, Class type) { String[] newArgNames = new String[argNames.length + 1]; System.arraycopy(argNames, 0, newArgNames, 0, argNames.length); newArgNames[argNames.length] = name; MethodType newMethodType = methodType.appendParameterTypes(type); return new Signature(newMethodType, newArgNames); } /** * Append an argument (name + type) to the signature. * * @param names the names of the arguments * @param types the types of the argument * @return a new signature with the added arguments */ public Signature appendArgs(String[] names, Class... types) { assert names.length == types.length : "names and types must be of the same length"; String[] newArgNames = new String[argNames.length + names.length]; System.arraycopy(argNames, 0, newArgNames, 0, argNames.length); System.arraycopy(names, 0, newArgNames, argNames.length, names.length); MethodType newMethodType = methodType.appendParameterTypes(types); return new Signature(newMethodType, newArgNames); } /** * Prepend an argument (name + type) to the signature. * * @param name the name of the argument * @param type the type of the argument * @return a new signature with the added arguments */ public Signature prependArg(String name, Class type) { String[] newArgNames = new String[argNames.length + 1]; System.arraycopy(argNames, 0, newArgNames, 1, argNames.length); newArgNames[0] = name; MethodType newMethodType = methodType.insertParameterTypes(0, type); return new Signature(newMethodType, newArgNames); } /** * Prepend arguments (names + types) to the signature. * * @param names the names of the arguments * @param types the types of the arguments * @return a new signature with the added arguments */ public Signature prependArgs(String[] names, Class... types) { String[] newArgNames = new String[argNames.length + names.length]; System.arraycopy(argNames, 0, newArgNames, names.length, argNames.length); System.arraycopy(names, 0, newArgNames, 0, names.length); MethodType newMethodType = methodType.insertParameterTypes(0, types); return new Signature(newMethodType, newArgNames); } /** * Insert an argument (name + type) into the signature. * * @param index the index at which to insert * @param name the name of the new argument * @param type the type of the new argument * @return a new signature with the added arguments */ public Signature insertArg(int index, String name, Class type) { return insertArgs(index, new String[]{name}, new Class[]{type}); } /** * Insert an argument (name + type) into the signature before the argument * with the given name. * * @param beforeName the name of the argument before which to insert * @param name the name of the new argument * @param type the type of the new argument * @return a new signature with the added arguments */ public Signature insertArg(String beforeName, String name, Class type) { return insertArgs(argOffset(beforeName), new String[]{name}, new Class[]{type}); } /** * Insert arguments (names + types) into the signature. * * @param index the index at which to insert * @param names the names of the new arguments * @param types the types of the new arguments * @return a new signature with the added arguments */ public Signature insertArgs(int index, String[] names, Class... types) { assert names.length == types.length : "names and types must be of the same length"; String[] newArgNames = new String[argNames.length + names.length]; System.arraycopy(names, 0, newArgNames, index, names.length); if (index != 0) System.arraycopy(argNames, 0, newArgNames, 0, index); if (argNames.length - index != 0) System.arraycopy(argNames, index, newArgNames, index + names.length, argNames.length - index); MethodType newMethodType = methodType.insertParameterTypes(index, types); return new Signature(newMethodType, newArgNames); } /** * Insert arguments (names + types) into the signature before the argument * with the given name. * * @param beforeName the name of the argument before which to insert * @param names the names of the new arguments * @param types the types of the new arguments * @return a new Signature with the added arguments */ public Signature insertArgs(String beforeName, String[] names, Class... types) { return insertArgs(argOffset(beforeName), names, types); } /** * Drops the first argument with the given name. * * @param name the name of the argument to drop * @return a new signature */ public Signature dropArg(String name) { String[] newArgNames = new String[argNames.length - 1]; MethodType newType = methodType; for (int i = 0, j = 0; i < argNames.length; i++) { if (argNames[i].equals(name)) { newType = newType.dropParameterTypes(j, j + 1); continue; } newArgNames[j++] = argNames[i]; } if (newType == null) { // arg name not found; should we error? return this; } return new Signature(newType, newArgNames); } /** * Drops the argument at the given index. * * @param index the index of the argument to drop * @return a new signature */ public Signature dropArg(int index) { assert index < argNames.length; String[] newArgNames = new String[argNames.length - 1]; if (index > 0) System.arraycopy(argNames, 0, newArgNames, 0, index); if (index < argNames.length - 1) System.arraycopy(argNames, index + 1, newArgNames, index, argNames.length - (index + 1)); MethodType newType = methodType.dropParameterTypes(index, index + 1); return new Signature(newType, newArgNames); } /** * Drop the last argument from this signature. * * @return a new signature */ public Signature dropLast() { return dropLast(1); } /** * Drop the specified number of last arguments from this signature. * * @param n number of arguments to drop * @return a new signature */ public Signature dropLast(int n) { return new Signature( methodType.dropParameterTypes(methodType.parameterCount() - n, methodType.parameterCount()), Arrays.copyOfRange(argNames, 0, argNames.length - n)); } /** * Drop the first argument from this signature. * * @return a new signature */ public Signature dropFirst() { return dropFirst(1); } /** * Drop the specified number of first arguments from this signature. * * @param n number of arguments to drop * @return a new signature */ public Signature dropFirst(int n) { return new Signature( methodType.dropParameterTypes(0, n), Arrays.copyOfRange(argNames, n, argNames.length)); } /** * Replace the named argument with a new name and type. * * @param oldName the old name of the argument * @param newName the new name of the argument; can be the same as old * @param newType the new type of the argument; can be the same as old * @return a new signature with the modified argument */ public Signature replaceArg(String oldName, String newName, Class newType) { int offset = argOffset(oldName); String[] newArgNames = argNames; if (!oldName.equals(newName)) { newArgNames = Arrays.copyOf(argNames, argNames.length); newArgNames[offset] = newName; } Class oldType = methodType.parameterType(offset); MethodType newMethodType = methodType; if (!oldType.equals(newType)) newMethodType = methodType.changeParameterType(offset, newType); return new Signature(newMethodType, newArgNames); } /** * Spread the trailing [] argument into its component type assigning given names. * * @param names names to use for the decomposed arguments * @param types types to use for the decomposed arguments * @return a new signature with decomposed arguments in place of the trailing array */ public Signature spread(String[] names, Class... types) { assert names.length == types.length : "names and types must be of the same length"; String[] newArgNames = new String[argNames.length - 1 + names.length]; System.arraycopy(names, 0, newArgNames, newArgNames.length - names.length, names.length); System.arraycopy(argNames, 0, newArgNames, 0, argNames.length - 1); MethodType newMethodType = methodType .dropParameterTypes(methodType.parameterCount() - 1, methodType.parameterCount()) .appendParameterTypes(types); return new Signature(newMethodType, newArgNames); } /** * Spread the trailing [] argument into its component type assigning given names. * * @param names names to use for the decomposed arguments * @return a new signature with decomposed arguments in place of the trailing array */ public Signature spread(String... names) { Class aryType = lastArgType(); assert lastArgType().isArray(); Class[] newTypes = new Class[names.length]; Arrays.fill(newTypes, aryType.getComponentType()); return spread(names, newTypes); } /** * Spread the trailing [] argument into its component type assigning given names. * * @param baseName base name of the spread arguments * @param count number of arguments into which the last argument will decompose * @return a new signature with decomposed arguments in place of the trailing array */ public Signature spread(String baseName, int count) { String[] spreadNames = new String[count]; for (int i = 0; i < count; i++) spreadNames[i] = baseName + i; return spread(spreadNames); } /** * Collect sequential arguments matching pattern into an array. They must have the same type. * * @param newName the name of the new array argument * @param oldPattern the pattern of arguments to collect * @return a new signature with an array argument where the collected arguments were */ public Signature collect(String newName, String oldPattern) { int start = -1; int newCount = 0; int gatherCount = 0; Class type = null; Pattern pattern = Pattern.compile(oldPattern); MethodType newType = type(); for (int i = 0; i < argNames.length; i++) { if (pattern.matcher(argName(i)).matches()) { gatherCount++; newType = newType.dropParameterTypes(newCount, newCount + 1); Class argType = argType(i); if (start == -1) start = i; if (type == null) { type = argType; } else { if (argType != type) { throw new InvalidTransformException("arguments matching " + pattern + " are not all of the same type"); } } } else { newCount++; } } if (start != -1) { String[] newNames = new String[newCount + 1]; // pre System.arraycopy(argNames, 0, newNames, 0, start); // vararg newNames[start] = newName; newType = newType.insertParameterTypes(start, Array.newInstance(type, 0).getClass()); // post if (newCount + 1 > start) { // args not at end System.arraycopy(argNames, start + gatherCount, newNames, start + 1, newCount - start); } return new Signature(newType, newNames); } return this; } /** * The current java.lang.invoke.MethodType for this Signature. * * @return the current method type */ public MethodType type() { return methodType; } /** * The current argument count. * * @return argument count of this signature */ public int argCount() { return argNames.length; } /** * The current argument names for this signature. * * @return the current argument names */ public String[] argNames() { return argNames; } /** * Retrieve the name of the argument at the given index. * * @param index the index from which to get the argument name * @return the argument name */ public String argName(int index) { return argNames[index]; } /** * Retrieve the offset of the given argument name in this signature's * arguments. If the argument name is not in the argument list, returns -1. * * @param name the argument name to search for * @return the offset at which the argument name was found or -1 */ public int argOffset(String name) { for (int i = 0; i < argNames.length; i++) { if (argNames[i].equals(name)) return i; } return -1; } /** * Retrieve the offset of the given argument name in this signature's * arguments. If the argument name is not in the argument list, returns -1. * * @param pattern the argument name to search for * @return the offset at which the argument name was found or -1 */ public int argOffsets(String pattern) { for (int i = 0; i < argNames.length; i++) { if (Pattern.compile(pattern).matcher(argNames[i]).find()) return i; } return -1; } /** * Get the first argument name. * * @return the first argument name */ public String firstArgName() { return argNames[0]; } /** * Get the last argument name. * * @return the last argument name */ public String lastArgName() { return argNames[argNames.length - 1]; } /** * Set the argument name at the given index. * * @param index the index at which to set the argument name * @param name the name to set * @return a new signature with the given name at the given index */ public Signature argName(int index, String name) { String[] argNames = Arrays.copyOf(argNames(), argNames().length); argNames[index] = name; return new Signature(type(), argNames); } /** * Get the argument type at the given index. * * @param index the index from which to get the argument type * @return the argument type */ public Class argType(int index) { return methodType.parameterType(index); } /** * Get the first argument type. * * @return the first argument type */ public Class firstArgType() { return methodType.parameterType(0); } /** * Get the last argument type. * * @return the last argument type */ public Class lastArgType() { return argType(methodType.parameterCount() - 1); } /** * Set the argument type at the given index. * * @param index the index at which to set the argument type * @param type the type to set * @return a new signature with the given type at the given index */ public Signature argType(int index, Class type) { return new Signature(type().changeParameterType(index, type), argNames()); } /** * Create a new signature containing the same return value as this one, but * only the specified arguments. * * @param permuteArgs the names of the arguments to preserve * @return the new signature */ public Signature permute(String... permuteArgs) { Pattern[] patterns = new Pattern[permuteArgs.length]; for (int i = 0; i < permuteArgs.length; i++) patterns[i] = Pattern.compile(permuteArgs[i]); List> types = new ArrayList<>(argNames.length); List names = new ArrayList<>(argNames.length); for (Pattern pattern : patterns) { for (int argOffset = 0; argOffset < argNames.length; argOffset++) { String arg = argNames[argOffset]; Matcher matcher = pattern.matcher(arg); if (matcher.find()) { types.add(methodType.parameterType(argOffset)); names.add(arg); } } } return new Signature(MethodType.methodType(methodType.returnType(), types.toArray(new Class[0])), names.toArray(new String[0])); } /** * Create a new signature containing the same return value as this one, but * omitting the specified arguments. Blacklisting to #permute's whitelisting. * * @param excludeArgs the names of the arguments to exclude * @return the new signature */ public Signature exclude(String... excludeArgs) { Pattern[] patterns = new Pattern[excludeArgs.length]; for (int i = 0; i < excludeArgs.length; i++) patterns[i] = Pattern.compile(excludeArgs[i]); List> types = new ArrayList<>(argNames.length); List names = new ArrayList<>(argNames.length); OUTER: for (int argOffset = 0; argOffset < argNames.length; argOffset++) { String arg = argNames[argOffset]; for (Pattern pattern : patterns) { Matcher matcher = pattern.matcher(arg); if (matcher.find()) continue OUTER; } // no matches, include types.add(methodType.parameterType(argOffset)); names.add(arg); } return new Signature(MethodType.methodType(methodType.returnType(), types.toArray(new Class[0])), names.toArray(new String[0])); } /** * Produce a method handle permuting the arguments in this signature using * the given permute arguments and targeting the given java.lang.invoke.MethodHandle. * * Example: * *
     * Signature sig = Signature.returning(String.class).appendArg("a", int.class).appendArg("b", int.class);
     * MethodHandle handle = handleThatTakesOneInt();
     * MethodHandle newHandle = sig.permuteTo(handle, "b");
     * 
* * @param target the method handle to target * @param permuteArgs the arguments to permute * @return a new handle that permutes appropriate positions based on the * given permute args */ public MethodHandle permuteWith(MethodHandle target, String... permuteArgs) { return MethodHandles.permuteArguments(target, methodType, to(permute(permuteArgs))); } /** * Produce a new SmartHandle by permuting this Signature's arguments to the * Signature of a target SmartHandle. The new SmartHandle's signature will * match this one, permuting those arguments and invoking the target handle. * * @param target the SmartHandle to use as a permutation target * @return a new SmartHandle that permutes this Signature's args into a call * to the target SmartHandle. * @see Signature#permuteWith(java.lang.invoke.MethodHandle, java.lang.String[]) */ public SmartHandle permuteWith(SmartHandle target) { String[] argNames = target.signature().argNames(); return new SmartHandle(this, permuteWith(target.handle(), argNames)); } /** * Generate an array of argument offsets based on permuting this signature * to the given signature. * * @param other the signature to target * @return an array of argument offsets that will permute to the given * signature */ public int[] to(Signature other) { return nonMatchingTo(other.argNames); } /** * Generate an array of argument offsets based on permuting this signature * to the given signature. Repeats are permitted, and the patterns will be * matched against actual argument names using regex matching. * * @param otherArgPatterns the argument name patterns to permute * @return an array of argument offsets that will permute to the matching * argument names */ public int[] to(String... otherArgPatterns) { return to(permute(otherArgPatterns)); } private int[] nonMatchingTo(String... otherArgNames) { int[] offsets = new int[otherArgNames.length]; int i = 0; for (String arg : otherArgNames) { int pos = -1; for (int offset = 0; offset < argNames.length; offset++) { if (argNames[offset].equals(arg)) { pos = offset; break; } } assert pos >= 0 : "argument not found: \"" + arg + "\""; offsets[i++] = pos; } return offsets; } } invokebinder-invokebinder-1.7/src/main/java/com/headius/invokebinder/SmartBinder.java000066400000000000000000001337541264574172000311320ustar00rootroot00000000000000/* * Copyright 2013-2014 headius. * * 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 com.headius.invokebinder; import java.io.PrintStream; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles.Lookup; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.regex.Pattern; /** * Maintains both a Binder, for building a series of transformations, and a * current Signature that maps symbolic names to arguments. Transformations * normally performed with Binder using argument indices can be done instead * using argument names and wildcards. * * TODO: Examples, or links to wiki examples. * * @author headius */ public class SmartBinder { private final Signature start; private final List signatures = new ArrayList<>(); private final Binder binder; private SmartBinder(Signature start, Binder binder) { this.start = start; this.signatures.add(start); this.binder = binder; } private SmartBinder(SmartBinder original, Signature next, Binder binder) { this.start = original.start; this.signatures.add(0, next); this.signatures.addAll(original.signatures); this.binder = binder; } /** * Get the current Signature for this binder. * * @return this binder's current Signature */ public Signature signature() { return signatures.get(0); } /** * Get the signature this binder started with. * * @return the signature this binder started with. */ public Signature baseSignature() { return signatures.get(signatures.size() - 1); } /** * Get the Binder instance associated with this SmartBinder. * * @return this SmartBinder's Binder instance */ public Binder binder() { return binder; } /** * Create a new SmartBinder from the given Signature. * * @param inbound the Signature to start from * @return a new SmartBinder */ public static SmartBinder from(Signature inbound) { return new SmartBinder(inbound, Binder.from(inbound.type())); } /** * Create a new SmartBinder from the given types and argument names. * * @param retType the type of the return value to start with * @param names the names of arguments * @param types the argument types * @return a new SmartBinder */ public static SmartBinder from(Class retType, String[] names, Class... types) { return from(Signature.returning(retType).appendArgs(names, types)); } /** * Create a new SmartBinder with from the given types and argument name. * * @param retType the type of the return value to start with * @param name the name of the sole argument * @param type the sole argument's type * @return a new SmartBinder */ public static SmartBinder from(Class retType, String name, Class type) { return from(Signature.returning(retType).appendArg(name, type)); } /** * Create a new SmartBinder from the given Signature, using the given * Lookup for any handle lookups. * * @param lookup the Lookup to use for handle lookups * @param inbound the Signature to start from * @return a new SmartBinder */ public static SmartBinder from(Lookup lookup, Signature inbound) { return new SmartBinder(inbound, Binder.from(lookup, inbound.type())); } /** * Create a new SmartBinder from the given types and argument names, * using the given Lookup for any handle lookups. * * @param lookup the Lookup to use for handle lookups * @param retType the type of the return value to start with * @param names the names of arguments * @param types the argument types * @return a new SmartBinder */ public static SmartBinder from(Lookup lookup, Class retType, String[] names, Class... types) { return from(lookup, Signature.returning(retType).appendArgs(names, types)); } /** * Create a new SmartBinder from the given types and argument name, * using the given Lookup for any handle lookups. * * @param lookup the Lookup to use for handle lookups * @param retType the type of the return value to start with * @param name the name of the sole arguments * @param type the sole argument's type * @return a new SmartBinder */ public static SmartBinder from(Lookup lookup, Class retType, String name, Class type) { return from(lookup, Signature.returning(retType).appendArg(name, type)); } /////////////////////////////////////////////////////////////////////////// // FOLDS, based on MethodHandles.foldArguments. /////////////////////////////////////////////////////////////////////////// /** * Pass all arguments to the given function and insert the resulting value * as newName into the argument list. * * @param newName the name of the new first argument where the fold * function's result will be passed * @param function a function which will receive all arguments and have its * return value inserted into the call chain * @return a new SmartBinder with the fold applied */ public SmartBinder fold(String newName, MethodHandle function) { return new SmartBinder(this, signature().prependArg(newName, function.type().returnType()), binder.fold(function)); } /** * Pass all arguments to the given function and insert the resulting value * as newName into the argument list. * * @param newName the name of the new first argument where the fold * function's result will be passed * @param function a function which will receive all arguments and have its * return value inserted into the call chain * @return a new SmartBinder with the fold applied */ public SmartBinder fold(String newName, SmartHandle function) { if (Arrays.equals(signature().argNames(), function.signature().argNames())) { return fold(newName, function.handle()); } else { return fold(newName, signature().changeReturn(function.signature().type().returnType()).permuteWith(function).handle()); } } /** * Pass all arguments to the given function and drop any result. * * @param function a function which will receive all arguments and have its * return value inserted into the call chain * @return a new SmartBinder with the fold applied */ public SmartBinder foldVoid(MethodHandle function) { return new SmartBinder(this, signature(), binder.foldVoid(function)); } /** * Pass all arguments to the given function and drop any result. * * @param function a function which will receive all arguments and have its * return value inserted into the call chain * @return a new SmartBinder with the fold applied */ public SmartBinder foldVoid(SmartHandle function) { if (Arrays.equals(signature().argNames(), function.signature().argNames())) { return foldVoid(function.handle()); } else { return foldVoid(signature().asFold(void.class).permuteWith(function).handle()); } } /** * Acquire a static folding function from the given target class, using the * given name and Lookup. Pass all arguments to that function and insert * the resulting value as newName into the argument list. * * @param newName the name of the new first argument where the fold * function's result will be passed * @param lookup the Lookup to use for acquiring a folding function * @param target the class on which to find the folding function * @param method the name of the method to become a folding function * @return a new SmartBinder with the fold applied */ public SmartBinder foldStatic(String newName, Lookup lookup, Class target, String method) { Binder newBinder = binder.foldStatic(lookup, target, method); return new SmartBinder(this, signature().prependArg(newName, newBinder.type().parameterType(0)), binder); } /** * Acquire a public static folding function from the given target class, * using the given name. Pass all arguments to that function and insert * the resulting value as newName into the argument list. * * @param newName the name of the new first argument where the fold * function's result will be passed * @param target the class on which to find the folding function * @param method the name of the method to become a folding function * @return a new SmartBinder with the fold applied */ public SmartBinder foldStatic(String newName, Class target, String method) { Binder newBinder = binder.foldStatic(target, method); return new SmartBinder(this, signature().prependArg(newName, newBinder.type().parameterType(0)), newBinder); } /** * Acquire a virtual folding function from the first argument's class, * using the given name and Lookup. Pass all arguments to that function and * insert the resulting value as newName into the argument list. * * @param newName the name of the new first argument where the fold * function's result will be passed * @param lookup the Lookup to use for acquiring a folding function * @param method the name of the method to become a folding function * @return a new SmartBinder with the fold applied */ public SmartBinder foldVirtual(String newName, Lookup lookup, String method) { Binder newBinder = binder.foldVirtual(lookup, method); return new SmartBinder(this, signature().prependArg(newName, newBinder.type().parameterType(0)), newBinder); } /** * Acquire a public virtual folding function from the first argument's * class, using the given name and Lookup. Pass all arguments to that * function and insert the resulting value as newName into the argument * list. * * @param newName the name of the new first argument where the fold * function's result will be passed * @param method the name of the method to become a folding function * @return a new SmartBinder with the fold applied */ public SmartBinder foldVirtual(String newName, String method) { Binder newBinder = binder.foldVirtual(method); return new SmartBinder(this, signature().prependArg(newName, newBinder.type().parameterType(0)), newBinder); } /////////////////////////////////////////////////////////////////////////// // PERMUTES, based on MethodHandles.permuteArguments /////////////////////////////////////////////////////////////////////////// /** * Using the argument names and order in the target Signature, permute the * arguments in this SmartBinder. Arguments may be duplicated or omitted * in the target Signature, but all arguments in the target must be defined * in this SmartBinder . * * @param target the Signature from which to derive a new argument list * @return a new SmartBinder with the permute applied */ public SmartBinder permute(Signature target) { return new SmartBinder(this, target, binder.permute(signature().to(target))); } /** * Using the argument names and order in the given targetNames, permute the * arguments in this SmartBinder. Arguments may be duplicated or omitted * in the targetNames array, but all arguments in the target must be * defined in this SmartBinder. * * @param targetNames the array of names from which to derive a new argument * list * @return a new SmartBinder with the permute applied */ public SmartBinder permute(String... targetNames) { return permute(signature().permute(targetNames)); } /** * Permute all parameters except the names given. Blacklisting to #permute's * whitelisting. * * @param excludeNames parameter patterns to exclude * @return a new SmartBinder with the exclude applied */ public SmartBinder exclude(String... excludeNames) { return permute(signature().exclude(excludeNames)); } /////////////////////////////////////////////////////////////////////////// // SPREADS, based on MethodHandle#asSpreader. /////////////////////////////////////////////////////////////////////////// /** * Spread a trailing array into the specified argument types. * * @param spreadNames the names for the spread out arguments * @param spreadTypes the types as which to spread the incoming array * @return a new SmartBinder with the spread applied */ public SmartBinder spread(String[] spreadNames, Class... spreadTypes) { return new SmartBinder(this, signature().spread(spreadNames, spreadTypes), binder.spread(spreadTypes)); } /** * Spread a trailing array into count number of arguments, using the * natural component type for the array. Build names for the arguments * using the given baseName plus the argument's index. * * Example: * Current binder has a signature of (int, String[])void. We want * to spread the strings into five arguments named "str". * * binder = binder.spread("str", 5) * * The resulting signature will have five trailing arguments named * "arg0" through "arg4". * * @param baseName the base name from which to create the new argument names * @param count the count of arguments to spread * @return a new SmartBinder with the spread applied */ public SmartBinder spread(String baseName, int count) { return new SmartBinder(this, signature().spread(baseName, count), binder.spread(count)); } /////////////////////////////////////////////////////////////////////////// // INSERTS, based on MethodHandles.insertArguments. /////////////////////////////////////////////////////////////////////////// /** * Insert an argument into the argument list at the given index with the * given name and value. * * @param index the index at which to insert the argument * @param name the name of the new argument * @param value the value of the new argument * @return a new SmartBinder with the insert applied */ public SmartBinder insert(int index, String name, Object value) { return new SmartBinder(this, signature().insertArg(index, name, value.getClass()), binder.insert(index, value)); } /** * Insert an argument into the argument list at the given index with the * given name and value. * * @param index the index at which to insert the argument * @param name the name of the new argument * @param value the value of the new argument * @return a new SmartBinder with the insert applied */ public SmartBinder insert(int index, String name, boolean value) { return new SmartBinder(this, signature().insertArg(index, name, boolean.class), binder.insert(index, value)); } /** * Insert an argument into the argument list at the given index with the * given name and value. * * @param index the index at which to insert the argument * @param name the name of the new argument * @param value the value of the new argument * @return a new SmartBinder with the insert applied */ public SmartBinder insert(int index, String name, byte value) { return new SmartBinder(this, signature().insertArg(index, name, byte.class), binder.insert(index, value)); } /** * Insert an argument into the argument list at the given index with the * given name and value. * * @param index the index at which to insert the argument * @param name the name of the new argument * @param value the value of the new argument * @return a new SmartBinder with the insert applied */ public SmartBinder insert(int index, String name, short value) { return new SmartBinder(this, signature().insertArg(index, name, short.class), binder.insert(index, value)); } /** * Insert an argument into the argument list at the given index with the * given name and value. * * @param index the index at which to insert the argument * @param name the name of the new argument * @param value the value of the new argument * @return a new SmartBinder with the insert applied */ public SmartBinder insert(int index, String name, char value) { return new SmartBinder(this, signature().insertArg(index, name, char.class), binder.insert(index, value)); } /** * Insert an argument into the argument list at the given index with the * given name and value. * * @param index the index at which to insert the argument * @param name the name of the new argument * @param value the value of the new argument * @return a new SmartBinder with the insert applied */ public SmartBinder insert(int index, String name, int value) { return new SmartBinder(this, signature().insertArg(index, name, int.class), binder.insert(index, value)); } /** * Insert an argument into the argument list at the given index with the * given name and value. * * @param index the index at which to insert the argument * @param name the name of the new argument * @param value the value of the new argument * @return a new SmartBinder with the insert applied */ public SmartBinder insert(int index, String name, long value) { return new SmartBinder(this, signature().insertArg(index, name, long.class), binder.insert(index, value)); } /** * Insert an argument into the argument list at the given index with the * given name and value. * * @param index the index at which to insert the argument * @param name the name of the new argument * @param value the value of the new argument * @return a new SmartBinder with the insert applied */ public SmartBinder insert(int index, String name, float value) { return new SmartBinder(this, signature().insertArg(index, name, float.class), binder.insert(index, value)); } /** * Insert an argument into the argument list at the given index with the * given name and value. * * @param index the index at which to insert the argument * @param name the name of the new argument * @param value the value of the new argument * @return a new SmartBinder with the insert applied */ public SmartBinder insert(int index, String name, double value) { return new SmartBinder(this, signature().insertArg(index, name, double.class), binder.insert(index, value)); } /** * Insert an argument into the argument list at the given index with the * given name and value. * * @param index the index at which to insert the argument * @param name the name of the new argument * @param type the type to use in the new signature * @param value the value of the new argument * @return a new SmartBinder with the insert applied */ public SmartBinder insert(int index, String name, Class type, Object value) { return new SmartBinder(this, signature().insertArg(index, name, type), binder.insert(index, type, value)); } /** * Insert arguments into the argument list at the given index with the * given names and values. * * @param index the index at which to insert the arguments * @param names the names of the new arguments * @param types the types of the new arguments * @param values the values of the new arguments * @return a new SmartBinder with the insert applied */ public SmartBinder insert(int index, String[] names, Class[] types, Object... values) { return new SmartBinder(this, signature().insertArgs(index, names, types), binder.insert(index, types, values)); } /** * Append the given argument to the argument list, assigning it the * given name. * * @param name the name of the new argument * @param value the value of the new argument * @return a new SmartBinder with the append applied */ public SmartBinder append(String name, Object value) { return new SmartBinder(this, signature().appendArg(name, value.getClass()), binder.append(value)); } /** * Append the given argument to the argument list, assigning it the * given name. * * @param name the name of the new argument * @param value the value of the new argument * @return a new SmartBinder with the append applied */ public SmartBinder append(String name, boolean value) { return new SmartBinder(this, signature().appendArg(name, boolean.class), binder.append(value)); } /** * Append the given argument to the argument list, assigning it the * given name. * * @param name the name of the new argument * @param value the value of the new argument * @return a new SmartBinder with the append applied */ public SmartBinder append(String name, byte value) { return new SmartBinder(this, signature().appendArg(name, byte.class), binder.append(value)); } /** * Append the given argument to the argument list, assigning it the * given name. * * @param name the name of the new argument * @param value the value of the new argument * @return a new SmartBinder with the append applied */ public SmartBinder append(String name, short value) { return new SmartBinder(this, signature().appendArg(name, short.class), binder.append(value)); } /** * Append the given argument to the argument list, assigning it the * given name. * * @param name the name of the new argument * @param value the value of the new argument * @return a new SmartBinder with the append applied */ public SmartBinder append(String name, char value) { return new SmartBinder(this, signature().appendArg(name, char.class), binder.append(value)); } /** * Append the given argument to the argument list, assigning it the * given name. * * @param name the name of the new argument * @param value the value of the new argument * @return a new SmartBinder with the append applied */ public SmartBinder append(String name, int value) { return new SmartBinder(this, signature().appendArg(name, int.class), binder.append(value)); } /** * Append the given argument to the argument list, assigning it the * given name. * * @param name the name of the new argument * @param value the value of the new argument * @return a new SmartBinder with the append applied */ public SmartBinder append(String name, long value) { return new SmartBinder(this, signature().appendArg(name, long.class), binder.append(value)); } /** * Append the given argument to the argument list, assigning it the * given name. * * @param name the name of the new argument * @param value the value of the new argument * @return a new SmartBinder with the append applied */ public SmartBinder append(String name, float value) { return new SmartBinder(this, signature().appendArg(name, float.class), binder.append(value)); } /** * Append the given argument to the argument list, assigning it the * given name. * * @param name the name of the new argument * @param value the value of the new argument * @return a new SmartBinder with the append applied */ public SmartBinder append(String name, double value) { return new SmartBinder(this, signature().appendArg(name, double.class), binder.append(value)); } /** * Append the given argument to the argument list, assigning it the * given name. * * @param name the name of the new argument * @param type the type to use in the new signature * @param value the value of the new argument * @return a new SmartBinder with the append applied */ public SmartBinder append(String name, Class type, Object value) { return new SmartBinder(this, signature().appendArg(name, type), binder.append(new Class[]{type}, value)); } /** * Append the given arguments to the argument list, assigning them the * given names. * * @param names the names of the new arguments * @param types the types to use in the new signature * @param values the values of the new arguments * @return a new SmartBinder with the append applied */ public SmartBinder append(String[] names, Class[] types, Object... values) { return new SmartBinder(this, signature().appendArgs(names, types), binder.append(types, values)); } /** * Prepend the given argument to the argument list, assigning it the * given name. * * @param name the name of the new argument * @param value the value of the new argument * @return a new SmartBinder with the prepend applied */ public SmartBinder prepend(String name, Object value) { return new SmartBinder(this, signature().prependArg(name, value.getClass()), binder.prepend(value)); } /** * Prepend the given argument to the argument list, assigning it the * given name. * * @param name the name of the new argument * @param value the value of the new argument * @return a new SmartBinder with the prepend applied */ public SmartBinder prepend(String name, boolean value) { return new SmartBinder(this, signature().prependArg(name, boolean.class), binder.prepend(value)); } /** * Prepend the given argument to the argument list, assigning it the * given name. * * @param name the name of the new argument * @param value the value of the new argument * @return a new SmartBinder with the prepend applied */ public SmartBinder prepend(String name, byte value) { return new SmartBinder(this, signature().prependArg(name, byte.class), binder.prepend(value)); } /** * Prepend the given argument to the argument list, assigning it the * given name. * * @param name the name of the new argument * @param value the value of the new argument * @return a new SmartBinder with the prepend applied */ public SmartBinder prepend(String name, short value) { return new SmartBinder(this, signature().prependArg(name, short.class), binder.prepend(value)); } /** * Prepend the given argument to the argument list, assigning it the * given name. * * @param name the name of the new argument * @param value the value of the new argument * @return a new SmartBinder with the prepend applied */ public SmartBinder prepend(String name, char value) { return new SmartBinder(this, signature().prependArg(name, char.class), binder.prepend(value)); } /** * Prepend the given argument to the argument list, assigning it the * given name. * * @param name the name of the new argument * @param value the value of the new argument * @return a new SmartBinder with the prepend applied */ public SmartBinder prepend(String name, int value) { return new SmartBinder(this, signature().prependArg(name, int.class), binder.prepend(value)); } /** * Prepend the given argument to the argument list, assigning it the * given name. * * @param name the name of the new argument * @param value the value of the new argument * @return a new SmartBinder with the prepend applied */ public SmartBinder prepend(String name, long value) { return new SmartBinder(this, signature().prependArg(name, long.class), binder.prepend(value)); } /** * Prepend the given argument to the argument list, assigning it the * given name. * * @param name the name of the new argument * @param value the value of the new argument * @return a new SmartBinder with the prepend applied */ public SmartBinder prepend(String name, float value) { return new SmartBinder(this, signature().prependArg(name, float.class), binder.prepend(value)); } /** * Prepend the given argument to the argument list, assigning it the * given name. * * @param name the name of the new argument * @param value the value of the new argument * @return a new SmartBinder with the prepend applied */ public SmartBinder prepend(String name, double value) { return new SmartBinder(this, signature().prependArg(name, double.class), binder.prepend(value)); } /** * Prepend the given argument to the argument list, assigning it the * given name. * * @param name the name of the new argument * @param type the type to use in the new signature * @param value the value of the new argument * @return a new SmartBinder with the prepend applied */ public SmartBinder prepend(String name, Class type, Object value) { return new SmartBinder(this, signature().prependArg(name, type), binder.prepend(type, value)); } /** * Prepend the given arguments to the argument list, assigning them the * given name. * * @param names the names of the new arguments * @param types the types to use in the new signature * @param values the values of the new arguments * @return a new SmartBinder with the prepend applied */ public SmartBinder prepend(String[] names, Class[] types, Object... values) { return new SmartBinder(this, signature().prependArgs(names, types), binder.prepend(types, values)); } /////////////////////////////////////////////////////////////////////////// // DROPS, based on MethodHandles.dropArguments. /////////////////////////////////////////////////////////////////////////// /** * Drop the argument with the given name. * * @param name the name of the argument to drop * @return a new SmartBinder with the drop applied */ public SmartBinder drop(String name) { int index = signature().argOffset(name); return new SmartBinder(this, signature().dropArg(index), binder.drop(index)); } /** * Drop the last argument. * * @return a new SmartBinder with the drop applied */ public SmartBinder dropLast() { return dropLast(1); } /** * Drop the last N arguments. * * @param count the count of arguments to drop * @return a new SmartBinder with the drop applied */ public SmartBinder dropLast(int count) { return new SmartBinder(this, signature().dropLast(count), binder.dropLast(count)); } /** * Drop the first argument. * * @return a new SmartBinder with the drop applied */ public SmartBinder dropFirst() { return dropFirst(1); } /** * Drop the first N arguments. * * @param count the count of arguments to drop * @return a new SmartBinder with the drop applied */ public SmartBinder dropFirst(int count) { return new SmartBinder(this, signature().dropFirst(count), binder.dropFirst(count)); } /////////////////////////////////////////////////////////////////////////// // COLLECTS, based on MethodHandle#asCollector /////////////////////////////////////////////////////////////////////////// /** * Collect arguments matching namePattern into an trailing array argument * named outName. * * The namePattern is a standard regular expression. * * @param outName the name of the new array argument * @param namePattern a pattern with which to match arguments for collecting * @return a new SmartBinder with the collect applied */ public SmartBinder collect(String outName, String namePattern) { int index = signature().argOffsets(namePattern); assert index >= 0 : "no arguments matching " + namePattern + " found in signature " + signature(); Signature newSignature = signature().collect(outName, namePattern); return new SmartBinder(this, newSignature, binder.collect(index, signature().argCount() - (newSignature.argCount() - 1), Array.newInstance(signature().argType(index), 0).getClass())); } /////////////////////////////////////////////////////////////////////////// // CASTS, based on MethodHandles.explicitCastArguments and MethodHandle.asType. /////////////////////////////////////////////////////////////////////////// /** * Cast the incoming arguments to the types in the given signature. The * argument count must match, but the names in the target signature are * ignored. * * @param target the Signature to which arguments should be cast * @return a new SmartBinder with the cast applied */ public SmartBinder cast(Signature target) { return new SmartBinder(target, binder.cast(target.type())); } /** * Cast the incoming arguments to the return and argument types given. The * argument count must match. * * @param returnType the return type for the casted signature * @param argTypes the types of the arguments for the casted signature * @return a new SmartBinder with the cast applied */ public SmartBinder cast(Class returnType, Class... argTypes) { return new SmartBinder(this, new Signature(returnType, argTypes, signature().argNames()), binder.cast(returnType, argTypes)); } /** * Cast the incoming arguments to the return, first argument type, and * remaining argument types. Provide for convenience when dealing with * virtual method argument lists, which frequently omit the target * object. * * @param returnType the return type for the casted signature * @param firstArg the type of the first argument for the casted signature * @param restArgs the types of the remaining arguments for the casted signature * @return a new SmartBinder with the cast applied. */ public SmartBinder castVirtual(Class returnType, Class firstArg, Class... restArgs) { return new SmartBinder(this, new Signature(returnType, firstArg, restArgs, signature().argNames()), binder.castVirtual(returnType, firstArg, restArgs)); } /** * Cast the named argument to the given type. * * @param name the name of the argument to cast * @param type the type to which that argument will be cast * @return a new SmartBinder with the cast applied */ public SmartBinder castArg(String name, Class type) { Signature newSig = signature().replaceArg(name, name, type); return new SmartBinder(this, newSig, binder.cast(newSig.type())); } /** * Cast the return value to the given type. * * Example: Our current signature is (String)String but the method this * handle will eventually call returns CharSequence. * * binder = binder.castReturn(CharSequence.class); * * Our handle will now successfully find and call the target method and * propagate the returned CharSequence as a String. * * @param type the new type for the return value * @return a new SmartBinder */ public SmartBinder castReturn(Class type) { return new SmartBinder(this, signature().changeReturn(type), binder.cast(type, binder.type().parameterArray())); } /////////////////////////////////////////////////////////////////////////// // FILTERS, based on MethodHandles.filterReturnValue and filterArguments. /////////////////////////////////////////////////////////////////////////// /** * Use the given filter function to transform the return value at this * point in the binder. The filter will be inserted into the handle, and * return values will pass through it before continuing. * * The filter's argument must match the expected return value downstream * from this point in the binder, and the return value must match the * return value at this point in the binder. * * @param filter the function to use to transform the return value at this point * @return a new SmartBinder with the filter applied */ public SmartBinder filterReturn(MethodHandle filter) { return new SmartBinder(this, signature().changeReturn(filter.type().returnType()), binder.filterReturn(filter)); } /** * Use the given filter function to transform the return value at this * point in the binder. The filter will be inserted into the handle, and * return values will pass through it before continuing. * * The filter's argument must match the expected return value downstream * from this point in the binder, and the return value must match the * return value at this point in the binder. * * @param filter the function to use to transform the return value at this point * @return a new SmartBinder with the filter applied */ public SmartBinder filterReturn(SmartHandle filter) { return new SmartBinder(this, signature().changeReturn(filter.signature().type().returnType()), binder.filterReturn(filter.handle())); } /////////////////////////////////////////////////////////////////////////// // INVOKES, terminating the handle chain at a concrete target /////////////////////////////////////////////////////////////////////////// /** * Terminate this binder by looking up the named virtual method on the * first argument's type. Perform the actual method lookup using the given * Lookup object. * * @param lookup the Lookup to use for handle lookups * @param name the name of the target virtual method * @return a SmartHandle with this binder's starting signature, bound * to the target method * @throws NoSuchMethodException if the named method with current signature's types does not exist * @throws IllegalAccessException if the named method is not accessible to the given Lookup */ public SmartHandle invokeVirtual(Lookup lookup, String name) throws NoSuchMethodException, IllegalAccessException { return new SmartHandle(start, binder.invokeVirtual(lookup, name)); } /** * Terminate this binder by looking up the named virtual method on the * first argument's type. Perform the actual method lookup using the given * Lookup object. If the lookup fails, a RuntimeException will be raised, * containing the actual reason. This method is for convenience in (for * example) field declarations, where checked exceptions noise up code * that can't recover anyway. * * Use this in situations where you would not expect your library to be * usable if the target method can't be acquired. * * @param lookup the Lookup to use for handle lookups * @param name the name of the target virtual method * @return a SmartHandle with this binder's starting signature, bound * to the target method */ public SmartHandle invokeVirtualQuiet(Lookup lookup, String name) { return new SmartHandle(start, binder.invokeVirtualQuiet(lookup, name)); } /** * Terminate this binder by looking up the named static method on the * given target type. Perform the actual method lookup using the given * Lookup object. * * @param lookup the Lookup to use for handle lookups * @param target the type on which to find the static method * @param name the name of the target static method * @return a SmartHandle with this binder's starting signature, bound * to the target method * @throws NoSuchMethodException if the named method with current signature's types does not exist * @throws IllegalAccessException if the named method is not accessible to the given Lookup */ public SmartHandle invokeStatic(Lookup lookup, Class target, String name) throws NoSuchMethodException, IllegalAccessException { return new SmartHandle(start, binder.invokeStatic(lookup, target, name)); } /** * Terminate this binder by looking up the named static method on the * given target type. Perform the actual method lookup using the given * Lookup object. If the lookup fails, a RuntimeException will be raised, * containing the actual reason. This method is for convenience in (for * example) field declarations, where checked exceptions noise up code * that can't recover anyway. * * Use this in situations where you would not expect your library to be * usable if the target method can't be acquired. * * @param lookup the Lookup to use for handle lookups * @param target the type on which to find the static method * @param name the name of the target static method * @return a SmartHandle with this binder's starting signature, bound * to the target method */ public SmartHandle invokeStaticQuiet(Lookup lookup, Class target, String name) { return new SmartHandle(start, binder.invokeStaticQuiet(lookup, target, name)); } /** * Terminate this binder by invoking the given target handle. The signature * of this binder is not compared to the signature of the given * SmartHandle. * * @param target the handle to invoke * @return a new SmartHandle with this binder's starting signature, bound * through to the given handle */ public SmartHandle invoke(SmartHandle target) { return new SmartHandle(start, binder.invoke(target.handle())); } /** * Terminate this binder by invoking the given target handle. * * @param target the handle to invoke * @return a new SmartHandle with this binder's starting signature, bound * through to the given handle */ public SmartHandle invoke(MethodHandle target) { return new SmartHandle(start, binder.invoke(target)); } /** * Terminate this binder by setting an array element based on the current * signature. The signature should have the array as its first argument, * an integer index as its second, and an appropriately-typed value as its * third. It should have a void return value. * * Invoking the resulting handle will (eventually) perform the array * assignment. * * @return a new SmartHandle with this binder's starting signature, bound * to an array assignment operation */ public SmartHandle arrayGet() { return new SmartHandle(start, binder.arrayGet()); } /** * Terminate this binder by getting an array element based on the current * signature. The signature should have the array as its first argument and * an integer index as its second, and an appropriately-typed return value. * * Invoking the resulting handle will (eventually) perform the array * assignment. * * @return a new SmartHandle with this binder's starting signature, bound * to an array dereference operation */ public SmartHandle arraySet() { return new SmartHandle(start, binder.arraySet()); } /** * Terminate this binder by returning its sole remaining argument. The * signature must take only one argument whose type matches the return * type. * * Invoking the resulting handle will (eventually) return the argument * passed in at this point. * * @return a new SmartHandle with this binder's starting signature that * simply returns its sole received argument */ public SmartHandle identity() { return new SmartHandle(start, binder.identity()); } /////////////////////////////////////////////////////////////////////////// // OTHER UTILITIES /////////////////////////////////////////////////////////////////////////// /** * Print this binder's current signature to System.out. * * @return this SmartBinder */ public SmartBinder printSignature() { return printSignature(System.out); } /** * Print this binder's current signature to the give PrintStream. * * @return this SmartBinder */ public SmartBinder printSignature(PrintStream ps) { ps.println(signature().toString()); return this; } /** * Produce a SmartHandle from this binder that invokes a leading * MethodHandle argument with the remaining arguments. * * @return a SmartHandle that invokes its leading MethodHandle argument */ public SmartHandle invoker() { return new SmartHandle(start, binder.invoker()); } /** * Filter the arguments matching the given pattern using the given filter function. * * @param pattern the regular expression pattern to match arguments * @param filter the MethodHandle to use to filter the arguments * @return a new SmartBinder with the filter applied */ public SmartBinder filter(String pattern, MethodHandle filter) { String[] argNames = signature().argNames(); Pattern pat = Pattern.compile(pattern); Binder newBinder = binder(); Signature newSig = signature(); for (int i = 0; i < argNames.length; i++) { if (pat.matcher(argNames[i]).matches()) { newBinder = newBinder.filter(i, filter); newSig = newSig.argType(i, filter.type().returnType()); } } return new SmartBinder(newSig, newBinder); } } invokebinder-invokebinder-1.7/src/main/java/com/headius/invokebinder/SmartHandle.java000066400000000000000000000323251264574172000311120ustar00rootroot00000000000000/* * Copyright 2013-2014 headius. * * 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 com.headius.invokebinder; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; import java.lang.invoke.MethodType; /** * A tuple of a Signature and a java.lang.invoke.MethodHandle, providing * features of both plus a number of MethodHandles.* methods in a simpler form. * * SmartHandle is provided as a way to couple a given MethodHandle to a * Signature, allowing future adaptation of the MethodHandle to proceed using * Signature's various shortcuts and conveniences. * * Example: * * * // A signature that only wants the "context" and "args" arguments * public static final Signature ARG_COUNT_CHECK_FOLD = Signature * .returning(void.class) * .appendArg("args", Object[].class); * * // The actual target signature for arg count checking, with min and max ints * public static final Signature ARG_COUNT_CHECK_SIGNATURE = Signature * .returning(int.class) * .appendArg("args", Object[].class) * .appendArg("min", int.class) * .appendArg("max", int.class); * * // A SmartHandle for the arity-checking method, using the fold and signature * // from above and inserting 1, 3 for min, max * SmartHandle arityCheck = SmartBinder * .from(ARITY_CHECK_FOLD) * .append("min", 1) * .append("max", 3) * .cast(ARITY_CHECK_SIGNATURE) * .invokeStaticQuiet(LOOKUP, ArgCountChecker.class, "checkArgumentCount"); * * // The variable-arity call contaings other arguments plus the Object[] args. * // Here, we can just fold with our arityCheck SmartHandle, which drops args * // we are not interested in, passes along the args array, and ignores the * // return value. * variableCall = SmartBinder * .from(VARIABLE_ARITY_SIGNATURE) * .foldVoid(arityCheck) * .invoke(directCall); * * * @author headius */ public class SmartHandle { /** * The signature associated with this smart handle */ private final Signature signature; /** * The MethodHandle associated with this smart handle */ private final MethodHandle handle; SmartHandle(Signature signature, MethodHandle handle) { this.signature = signature; this.handle = handle; } /** * Create a new SmartHandle from the given Signature and MethodHandle. * * @param signature the signature for the new smart handle * @param handle the method handle for the new smart handle * @return a new SmartHandle */ public static SmartHandle from(Signature signature, MethodHandle handle) { return new SmartHandle(signature, handle); } /** * Create a new SmartHandle by performing a lookup on the given target class * for the given method name with the given signature. * * @param lookup the MethodHandles.Lookup object to use * @param target the class where the method is located * @param name the name of the method * @param signature the signature of the method * @return a new SmartHandle based on the signature and looked-up MethodHandle */ public static SmartHandle findStaticQuiet(Lookup lookup, Class target, String name, Signature signature) { try { return new SmartHandle(signature, lookup.findStatic(target, name, signature.type())); } catch (NoSuchMethodException | IllegalAccessException e) { throw new RuntimeException(e); } } /** * Get the Signature of this SmartHandle. * * @return the Signature of this SmartHandle */ public Signature signature() { return signature; } /** * Get the MethodHandle of this SmartHandle. * * @return the MethodHandle of this SmartHandle */ public MethodHandle handle() { return handle; } /** * Apply an argument into the handle at the given index, returning a new * SmartHandle. The new handle will use the given value for the argument at * the given index, accepting one fewer argument as a result. In other words, * fix that argument (partial application) into the given handle. * * @param index the index of the argument in the new SmartHandle's Signature * @param arg the argument value * @return a new SmartHandle that already has applied the given argument */ public SmartHandle apply(int index, Object arg) { return new SmartHandle(signature.dropArg(index), MethodHandles.insertArguments(handle, index, arg)); } /** * Apply an argument into the handle at the given name, returning a new * SmartHandle. The new handle will use the given value for the argument at * the given index, accepting one fewer argument as a result. In other words, * fix that argument (partial application) into the given handle. * * @param name the name of the argument in the new SmartHandle's Signature * @param arg the argument value * @return a new SmartHandle that already has applied the given argument */ public SmartHandle apply(String name, Object arg) { return new SmartHandle(signature.dropArg(name), MethodHandles.insertArguments(handle, signature.argOffset(name), arg)); } /** * Apply an argument into the handle at the end, returning a new * SmartHandle. The new handle will use the given value for the last * argument, accepting one fewer argument as a result. In other words, * fix that argument (partial application) into the given handle. * * @param arg the argument value * @return a new SmartHandle that already has applied the given argument */ public SmartHandle applyLast(Object arg) { return new SmartHandle(signature.dropLast(), MethodHandles.insertArguments(handle, signature.type().parameterCount(), arg)); } /** * Drop an argument name and type from the handle at the given index, returning a new * SmartHandle. * * @param beforeName name before which the dropped argument goes * @param newName name of the argument * @param type type of the argument * @return a new SmartHandle with the additional argument */ public SmartHandle drop(String beforeName, String newName, Class type) { return new SmartHandle(signature.insertArg(beforeName, newName, type), MethodHandles.dropArguments(handle, signature.argOffset(beforeName), type)); } /** * Drop an argument from the handle at the given index, returning a new * SmartHandle. * * @param index index before which the dropped argument goes * @param newName name of the argument * @param type type of the argument * @return a new SmartHandle with the additional argument */ public SmartHandle drop(int index, String newName, Class type) { return new SmartHandle(signature.insertArg(index, newName, type), MethodHandles.dropArguments(handle, index, type)); } /** * Drop an argument from the handle at the end, returning a new * SmartHandle. * * @param newName name of the argument * @param type type of the argument * @return a new SmartHandle with the additional argument */ public SmartHandle dropLast(String newName, Class type) { return new SmartHandle(signature.appendArg(newName, type), MethodHandles.dropArguments(handle, signature.argOffset(newName), type)); } /** * Use this SmartHandle as a test to guard target and fallback handles. * * @param target the "true" path for this handle's test * @param fallback the "false" path for this handle's test * @return a MethodHandle that performs the test and branch */ public MethodHandle guard(MethodHandle target, MethodHandle fallback) { return MethodHandles.guardWithTest(handle, target, fallback); } /** * Use this SmartHandle as a test to guard target and fallback handles. * * @param target the "true" path for this handle's test * @param fallback the "false" path for this handle's test * @return a new SmartHandle that performs the test and branch */ public SmartHandle guard(SmartHandle target, SmartHandle fallback) { return new SmartHandle(target.signature, MethodHandles.guardWithTest(handle, target.handle, fallback.handle)); } /** * Bind the first argument of this SmartHandle to the given object, * returning a new adapted handle. * * @param obj the object to which to bind this handle's first argument * @return a new SmartHandle with the first argument dropped in favor of obj */ public SmartHandle bindTo(Object obj) { return new SmartHandle(signature.dropFirst(), handle.bindTo(obj)); } /** * Create a new SmartHandle that converts arguments from the given type to * the current signature's type, using the same argument names. This conversion * is equivalent to MethodHandle#asType. * * @param incoming the target MethodType from which arguments will be converted * @return a new SmartHandle that accepts the given argument types */ public SmartHandle convert(MethodType incoming) { return new SmartHandle(new Signature(incoming, signature.argNames()), handle.asType(incoming)); } /** * Create a new SmartHandle that converts arguments from the given return * type and argument types to the current signature's type, using the same * argument names. This conversion is equivalent to MethodHandle#asType. * * @param returnType the return type of the new handle * @param argTypes the argument types of the new handle * @return a new SmartHandle that accepts the given argument types */ public SmartHandle convert(Class returnType, Class... argTypes) { return convert(MethodType.methodType(returnType, argTypes)); } /** * Create a new SmartHandle that converts arguments from the given signature to * the current signature's type with the new argument names. This conversion * is equivalent to MethodHandle#asType. * * @param incoming the target MethodType from which arguments will be converted * @return a new SmartHandle that accepts the given argument types */ public SmartHandle convert(Signature incoming) { return new SmartHandle(incoming, handle.asType(incoming.type())); } /** * Create a new SmartHandle that casts arguments from the given type to * the current signature's type, using the same argument names. This casting * is equivalent to MethodHandles#explicitCastArguments. * * @param incoming the target MethodType from which arguments will be converted * @return a new SmartHandle that accepts the given argument types */ public SmartHandle cast(MethodType incoming) { return new SmartHandle(new Signature(incoming, signature.argNames()), MethodHandles.explicitCastArguments(handle, incoming)); } /** * Create a new SmartHandle that casts arguments from the given signature to * the current signature's type with the new argument names. This casting * is equivalent to MethodHandle#asType. * * @param incoming the target MethodType from which arguments will be converted * @return a new SmartHandle that accepts the given argument types */ public SmartHandle cast(Signature incoming) { return new SmartHandle(incoming, MethodHandles.explicitCastArguments(handle, incoming.type())); } /** * Create a new SmartHandle that casts arguments from the given return * type and argument types to the current signature's type, using the same * argument names. This casting is equivalent to MethodHandle#asType. * * @param returnType the return type of the new handle * @param argTypes the argument types of the new handle * @return a new SmartHandle that accepts the given argument types */ public SmartHandle cast(Class returnType, Class... argTypes) { return cast(MethodType.methodType(returnType, argTypes)); } /** * Replace the return value with the given value, performing no other * processing of the original value. * * @param type the type for the new return value * @param value the new value to return * @return a new SmartHandle that returns the given value */ public SmartHandle returnValue(Class type, Object value) { return new SmartHandle(signature.changeReturn(type), MethodHandles.filterReturnValue(handle, MethodHandles.constant(type, value))); } /** * A human-readable String representation of this SamrtHandle. * * @return a String representation of this handle */ @Override public String toString() { return signature.toString() + "=>" + handle; } } invokebinder-invokebinder-1.7/src/main/java/com/headius/invokebinder/transform/000077500000000000000000000000001264574172000300535ustar00rootroot00000000000000invokebinder-invokebinder-1.7/src/main/java/com/headius/invokebinder/transform/Cast.java000066400000000000000000000035131264574172000316120ustar00rootroot00000000000000/* * Copyright 2012-2014 headius. * * 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 com.headius.invokebinder.transform; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; /** * A casting transform. * * Equivalent call: MethodHandles.explicitCastArguments(MethodHandle, MethodType). */ public class Cast extends Transform { private final MethodType type; public Cast(MethodType type) { this.type = type; } public MethodHandle up(MethodHandle target) { // If target's return type is void, it is replaced with (Object)null. // If incoming signature expects something else, additional cast is required. // TODO: Is this a bug in JDK? if (target.type().returnType() == void.class) { target = MethodHandles.explicitCastArguments(target, type); return MethodHandles.explicitCastArguments(target, type); } else { return MethodHandles.explicitCastArguments(target, type); } } public MethodType down(MethodType type) { for (int i = 0; i < type.parameterCount(); i++) { type = type.changeParameterType(i, type.parameterArray()[i]); } return type; } public String toString() { return "cast args to " + type; } } invokebinder-invokebinder-1.7/src/main/java/com/headius/invokebinder/transform/Catch.java000066400000000000000000000027371264574172000317510ustar00rootroot00000000000000/* * Copyright 2012-2014 headius. * * 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 com.headius.invokebinder.transform; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; /** * An exception-handling transform. * * Equivalent call: MethodHandles.catchException(MethodHandle, Class, MethodHandle). */ public class Catch extends Transform { private final Class throwable; private final MethodHandle function; public Catch(Class throwable, MethodHandle function) { this.throwable = throwable; this.function = function; } public MethodHandle up(MethodHandle target) { return MethodHandles.catchException(target, throwable, function); } public MethodType down(MethodType type) { return type; } public String toString() { return "catch exception type " + throwable + " using " + function; } } invokebinder-invokebinder-1.7/src/main/java/com/headius/invokebinder/transform/Collect.java000066400000000000000000000066021264574172000323070ustar00rootroot00000000000000/* * Copyright 2012-2014 headius. * * 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 com.headius.invokebinder.transform; import com.headius.invokebinder.Binder; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; /** * An argument-boxing transform with a fixed incoming size. * * Equivalent call: MethodHandle.asCollector(Class, int) */ public class Collect extends Transform { private final MethodType source; private final int index; private final int count; private final Class arrayType; public Collect(MethodType source, int index, Class arrayType) { this.source = source; this.index = index; this.count = source.parameterCount() - index; this.arrayType = arrayType; } public Collect(MethodType source, int index, int count, Class arrayType) { this.source = source; this.index = index; this.count = count; this.arrayType = arrayType; } public MethodHandle up(MethodHandle target) { if (index + count == source.parameterCount()) { // fast path for tail args return target.asCollector(arrayType, count); } else { int[] movePermute = new int[source.parameterCount()]; int[] moveBackPermute = new int[target.type().parameterCount()]; // pre for (int i = 0; i < index; i++) { movePermute[i] = i; moveBackPermute[i] = i; } // post int shifted = 0; for (int i = index; i + count < movePermute.length; i++, shifted++) movePermute[i] = i + count; for (int i = index; i + 1 < moveBackPermute.length; i++) moveBackPermute[i + 1] = i; // collected args for (int i = index + shifted; i < movePermute.length; i++) movePermute[i] = i - shifted; moveBackPermute[index] = moveBackPermute.length - 1; return Binder.from(source) .permute(movePermute) .collect(source.parameterCount() - count, arrayType) .permute(moveBackPermute) .invoke(target); } } public MethodType down(MethodType type) { assertTypesAreCompatible(); return type .dropParameterTypes(index, index + count) .insertParameterTypes(index, arrayType); } private void assertTypesAreCompatible() { Class componentType = arrayType.getComponentType(); for (int i = index; i < index + count; i++) { Class in = source.parameterType(i); assert in.isAssignableFrom(componentType) : "incoming type " + in.getName() + " not compatible with " + componentType.getName() + "[]"; } } public String toString() { return "collect at " + index + " into " + arrayType.getName(); } } invokebinder-invokebinder-1.7/src/main/java/com/headius/invokebinder/transform/Convert.java000066400000000000000000000033711264574172000323420ustar00rootroot00000000000000/* * Copyright 2012-2014 headius. * * 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 com.headius.invokebinder.transform; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; /** * An argument conversion transform. * * Equivalent call: MethodHandles.asType(MethodHandle, MethodType). */ public class Convert extends Transform { private final MethodType type; public Convert(MethodType type) { this.type = type; } public MethodHandle up(MethodHandle target) { // If target's return type is void, it is replaced with (Object)null. // If incoming signature expects something else, additional cast is required. // TODO: Is this a bug in JDK? if (target.type().returnType() == void.class) { return MethodHandles.explicitCastArguments(target.asType(type), type); } else { return target.asType(type); } } public MethodType down(MethodType type) { for (int i = 0; i < type.parameterCount(); i++) { type = type.changeParameterType(i, type.parameterArray()[i]); } return type; } public String toString() { return "convert args to " + type; } } invokebinder-invokebinder-1.7/src/main/java/com/headius/invokebinder/transform/Drop.java000066400000000000000000000027261264574172000316310ustar00rootroot00000000000000/* * Copyright 2012-2014 headius. * * 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 com.headius.invokebinder.transform; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.util.Arrays; /** * An argument drop transform. * * Equivalent call: MethodHandles.dropArguments(MethodHandle, int, MethodType). */ public class Drop extends Transform { private final int position; private final Class[] types; public Drop(int position, Class... types) { this.position = position; this.types = types; } public MethodHandle up(MethodHandle target) { return MethodHandles.dropArguments(target, position, types); } public MethodType down(MethodType type) { return type.dropParameterTypes(position, position + types.length); } public String toString() { return "drop " + Arrays.toString(types) + " at " + position; } } invokebinder-invokebinder-1.7/src/main/java/com/headius/invokebinder/transform/Filter.java000066400000000000000000000031451264574172000321460ustar00rootroot00000000000000/* * Copyright 2012-2014 headius. * * 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 com.headius.invokebinder.transform; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.util.Arrays; /** * An argument-filtering transform. * * Equivalent call: MethodHandles.filterArguments(MethodHandle, int, MethodHandle...). */ public class Filter extends Transform { private final int index; private final MethodHandle[] functions; public Filter(int index, MethodHandle... functions) { this.index = index; this.functions = functions; } public MethodHandle up(MethodHandle target) { return MethodHandles.filterArguments(target, index, functions); } public MethodType down(MethodType type) { for (int i = 0; i < functions.length; i++) { type = type.changeParameterType(index + i, functions[i].type().returnType()); } return type; } public String toString() { return "fold args from " + index + " with " + Arrays.toString(functions); } } invokebinder-invokebinder-1.7/src/main/java/com/headius/invokebinder/transform/FilterReturn.java000066400000000000000000000034011264574172000333410ustar00rootroot00000000000000/* * Copyright 2012-2014 headius. * * 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 com.headius.invokebinder.transform; import com.headius.invokebinder.InvalidTransformException; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; /** * An return-filtering transform. * * Equivalent call: MethodHandles.filterReturn(MethodHandle, MethodHandle). */ public class FilterReturn extends Transform { private final MethodHandle function; public FilterReturn(MethodHandle function) { this.function = function; } public MethodHandle up(MethodHandle target) { return MethodHandles.filterReturnValue(target, function); } public MethodType down(MethodType type) { int count = function.type().parameterCount(); switch (count) { case 0: return type.changeReturnType(void.class); case 1: return type.changeReturnType(function.type().parameterType(0)); default: throw new InvalidTransformException("return filter " + function + " does not accept zero or one argument"); } } public String toString() { return "filter return with " + function; } } invokebinder-invokebinder-1.7/src/main/java/com/headius/invokebinder/transform/Fold.java000066400000000000000000000026421264574172000316060ustar00rootroot00000000000000/* * Copyright 2012-2014 headius. * * 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 com.headius.invokebinder.transform; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; /** * An argument-folding transform. * * Equivalent call: MethodHandles.foldArguments(MethodHandle, MethodHandle). */ public class Fold extends Transform { private final MethodHandle function; public Fold(MethodHandle function) { this.function = function; } public MethodHandle up(MethodHandle target) { return MethodHandles.foldArguments(target, function); } public MethodType down(MethodType type) { if (function.type().returnType() == void.class) return type; return type.insertParameterTypes(0, function.type().returnType()); } public String toString() { return "fold args with " + function; } } invokebinder-invokebinder-1.7/src/main/java/com/headius/invokebinder/transform/Insert.java000066400000000000000000000067131264574172000321710ustar00rootroot00000000000000/* * Copyright 2012-2014 headius. * * 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 com.headius.invokebinder.transform; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.util.Arrays; /** * An argument insertion transform. * * Equivalent call: MethodHandles.insertArguments(MethodHandle, int, Object...). */ public class Insert extends Transform { private final int position; private final Class[] types; private final Object[] values; public Insert(int position, Object... values) { this.position = position; this.values = values; Class[] types = new Class[values.length]; for (int i = 0; i < values.length; i++) { types[i] = values[i].getClass(); } this.types = types; } public Insert(int position, boolean value) { this.position = position; this.values = new Object[]{value}; this.types = new Class[]{boolean.class}; } public Insert(int position, byte value) { this.position = position; this.values = new Object[]{value}; this.types = new Class[]{byte.class}; } public Insert(int position, short value) { this.position = position; this.values = new Object[]{value}; this.types = new Class[]{short.class}; } public Insert(int position, char value) { this.position = position; this.values = new Object[]{value}; this.types = new Class[]{char.class}; } public Insert(int position, int value) { this.position = position; this.values = new Object[]{value}; this.types = new Class[]{int.class}; } public Insert(int position, long value) { this.position = position; this.values = new Object[]{value}; this.types = new Class[]{long.class}; } public Insert(int position, float value) { this.position = position; this.values = new Object[]{value}; this.types = new Class[]{float.class}; } public Insert(int position, double value) { this.position = position; this.values = new Object[]{value}; this.types = new Class[]{double.class}; } public Insert(int position, Class[] types, Object... values) { this.position = position; this.values = values; this.types = types; } public MethodHandle up(MethodHandle target) { return MethodHandles.insertArguments(target, position, values); } public MethodType down(MethodType type) { return type.insertParameterTypes(position, types); } public String toString() { return "insert " + Arrays.toString(types()) + " at " + position; } private Class[] types() { Class[] types = new Class[values.length]; for (int i = 0; i < types.length; i++) { types[i] = values[i].getClass(); } return types; } } invokebinder-invokebinder-1.7/src/main/java/com/headius/invokebinder/transform/Permute.java000066400000000000000000000037311264574172000323430ustar00rootroot00000000000000/* * Copyright 2012-2014 headius. * * 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 com.headius.invokebinder.transform; import com.headius.invokebinder.InvalidTransformException; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.util.Arrays; /** * A permutation transform. * * Equivalent call: MethodHandles.permuteArguments(MethodHandle, MethodType, int...) */ public class Permute extends Transform { private final MethodType source; private final int[] reorder; public Permute(MethodType source, int... reorder) { this.source = source; this.reorder = reorder; } public MethodHandle up(MethodHandle target) { return MethodHandles.permuteArguments(target, source, reorder); } public MethodType down(MethodType type) { Class[] types = new Class[reorder.length]; for (int i = 0; i < reorder.length; i++) { int typeIndex = reorder[i]; if (typeIndex < 0 || typeIndex >= type.parameterCount()) { throw new InvalidTransformException("one or more permute indices (" + Arrays.toString(reorder) + ") out of bounds for " + source); } types[i] = type.parameterType(reorder[i]); } return MethodType.methodType(type.returnType(), types); } public String toString() { return "permute " + source + " with " + Arrays.toString(reorder); } } invokebinder-invokebinder-1.7/src/main/java/com/headius/invokebinder/transform/Spread.java000066400000000000000000000034101264574172000321320ustar00rootroot00000000000000/* * Copyright 2012-2014 headius. * * 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 com.headius.invokebinder.transform; import com.headius.invokebinder.InvalidTransformException; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; /** * An array-spreading transform. * * Equivalent call: MethodHandles.spreadInvoker(MethodType, int).bindTo(MethodHandle) */ public class Spread extends Transform { private final MethodType source; private final Class[] spreadTypes; public Spread(MethodType source, Class... spreadTypes) { this.source = source; this.spreadTypes = spreadTypes; } public MethodHandle up(MethodHandle target) { return target.asSpreader(source.parameterType(source.parameterCount() - 1), spreadTypes.length); } public MethodType down(MethodType type) { int last = source.parameterCount() - 1; if (!source.parameterArray()[last].isArray()) { throw new InvalidTransformException("trailing argument is not []: " + source); } type = type.dropParameterTypes(last, last + 1); return type.appendParameterTypes(spreadTypes); } public String toString() { return "spread " + source + " to " + down(source); } } invokebinder-invokebinder-1.7/src/main/java/com/headius/invokebinder/transform/Transform.java000066400000000000000000000027121264574172000326730ustar00rootroot00000000000000/* * Copyright 2012-2014 headius. * * 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 com.headius.invokebinder.transform; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; /** * Abstract reprensentation of some handle transform. */ public abstract class Transform { /** * Apply this transform upward from the given MethodHandle, producing * a new handle. * * @param target the target handle * @return the adapted handle */ public abstract MethodHandle up(MethodHandle target); /** * Apply this transform downward from an incoming MethodType, producing * a new type. * * @param source the source type * @return the new type */ public abstract MethodType down(MethodType source); /** * Return a string representation of this transform. * * @return a string representation of this transform */ public abstract String toString(); } invokebinder-invokebinder-1.7/src/main/java/com/headius/invokebinder/transform/TryFinally.java000066400000000000000000000054701264574172000330210ustar00rootroot00000000000000/* * Copyright 2012-2014 headius. * * 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 com.headius.invokebinder.transform; import com.headius.invokebinder.Binder; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; /** * An try-finally transform. * * Equivalent call: A combination of folds and catches. * * MethodHandle exceptionHandler = [drop exception and invoke post logic] * target = MethodHandles.catchException(target, Throwable.class, exceptionHandler) * target = MethodHandles.foldArguments(post, target) */ public class TryFinally extends Transform { private final MethodHandle post; public TryFinally(MethodHandle post) { this.post = post; } public MethodHandle up(MethodHandle target) { MethodHandle exceptionHandler = Binder .from(target.type().insertParameterTypes(0, Throwable.class).changeReturnType(void.class)) .drop(0) .invoke(post); MethodHandle rethrow = Binder .from(target.type().insertParameterTypes(0, Throwable.class)) .fold(exceptionHandler) .drop(1, target.type().parameterCount()) .throwException(); target = MethodHandles.catchException(target, Throwable.class, rethrow); // if target returns a value, we must return it regardless of post MethodHandle realPost = post; if (target.type().returnType() != void.class) { // modify post to ignore return value MethodHandle newPost = Binder .from(target.type().insertParameterTypes(0, target.type().returnType()).changeReturnType(void.class)) .drop(0) .invoke(post); // fold post into an identity chain that only returns the value realPost = Binder .from(target.type().insertParameterTypes(0, target.type().returnType())) .fold(newPost) .drop(1, target.type().parameterCount()) .identity(); } return MethodHandles.foldArguments(realPost, target); } public MethodType down(MethodType type) { return type; } public String toString() { return "try/finally with " + post; } } invokebinder-invokebinder-1.7/src/main/java/com/headius/invokebinder/transform/Varargs.java000066400000000000000000000037261264574172000323330ustar00rootroot00000000000000/* * Copyright 2012-2014 headius. * * 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 com.headius.invokebinder.transform; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; /** * An argument-boxing transform. * * Equivalent call: MethodHandle.asVarargsCollector(Class) */ public class Varargs extends Transform { private final MethodType source; private int index; private final Class arrayType; public Varargs(MethodType source, int index, Class arrayType) { this.source = source; this.index = index; this.arrayType = arrayType; } public MethodHandle up(MethodHandle target) { return target.asVarargsCollector(arrayType).asType(source); } public MethodType down(MethodType type) { assertTypesAreCompatible(); return type .dropParameterTypes(index, source.parameterCount()) .appendParameterTypes(arrayType); } private void assertTypesAreCompatible() { Class componentType = arrayType.getComponentType(); for (int i = index; i < source.parameterCount(); i++) { Class in = source.parameterType(i); assert componentType.isAssignableFrom(in) : "incoming type " + in.getName() + " not compatible with " + componentType.getName() + "[]"; } } public String toString() { return "varargs at " + index + " into " + arrayType.getName(); } } invokebinder-invokebinder-1.7/src/test/000077500000000000000000000000001264574172000202735ustar00rootroot00000000000000invokebinder-invokebinder-1.7/src/test/java/000077500000000000000000000000001264574172000212145ustar00rootroot00000000000000invokebinder-invokebinder-1.7/src/test/java/com/000077500000000000000000000000001264574172000217725ustar00rootroot00000000000000invokebinder-invokebinder-1.7/src/test/java/com/headius/000077500000000000000000000000001264574172000234145ustar00rootroot00000000000000invokebinder-invokebinder-1.7/src/test/java/com/headius/invokebinder/000077500000000000000000000000001264574172000260735ustar00rootroot00000000000000invokebinder-invokebinder-1.7/src/test/java/com/headius/invokebinder/BinderTest.java000066400000000000000000001107201264574172000310020ustar00rootroot00000000000000package com.headius.invokebinder; import org.junit.Test; import static org.junit.Assert.*; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; import java.lang.invoke.MethodType; import java.lang.reflect.Method; /** * @author headius */ public class BinderTest { private static final Lookup LOOKUP = MethodHandles.lookup(); @Test public void testFrom() throws Throwable { MethodHandle target = Subjects.concatHandle(); Binder binder1 = Binder .from(String.class, String.class, Object.class) .drop(1); MethodHandle handle = Binder .from(binder1) .insert(1, "world") .invoke(target); assertEquals(MethodType.methodType(String.class, String.class, Object.class), handle.type()); assertEquals("Hello, world", (String) handle.invokeExact("Hello, ", new Object())); } @Test public void testInsertPrimitive() throws Throwable { Binder b1 = Binder .from(void.class) .insert(0, true); assertEquals(MethodType.methodType(void.class, boolean.class), b1.type()); Binder b2 = Binder .from(void.class) .insert(0, (byte)1); assertEquals(MethodType.methodType(void.class, byte.class), b2.type()); Binder b3 = Binder .from(void.class) .insert(0, (short)1); assertEquals(MethodType.methodType(void.class, short.class), b3.type()); Binder b4 = Binder .from(void.class) .insert(0, (char)1); assertEquals(MethodType.methodType(void.class, char.class), b4.type()); Binder b5 = Binder .from(void.class) .insert(0, 1); assertEquals(MethodType.methodType(void.class, int.class), b5.type()); Binder b6 = Binder .from(void.class) .insert(0, 1L); assertEquals(MethodType.methodType(void.class, long.class), b6.type()); Binder b7 = Binder .from(void.class) .insert(0, 1.0F); assertEquals(MethodType.methodType(void.class, float.class), b7.type()); Binder b8 = Binder .from(void.class) .insert(0, 1.0); assertEquals(MethodType.methodType(void.class, double.class), b8.type()); MethodHandle target = intLongHandle(); MethodHandle handle = Binder .from(String.class) .insert(0, new Class[]{int.class, long.class}, 1, 1L) .invoke(target); assertEquals(MethodType.methodType(String.class), handle.type()); assertEquals("intLong ok", (String) handle.invokeExact()); } @Test public void testTo() throws Throwable { Binder otherBinder = Binder .from(String.class, String.class, int.class) .drop(1) .insert(1, ", world"); Binder thisBinder = Binder .from(String.class) .insert(0, "Hello") .insert(1, 1); Binder newBinder = thisBinder.to(otherBinder); assertEquals(MethodType.methodType(String.class, String.class, String.class), otherBinder.type()); assertEquals(MethodType.methodType(String.class, String.class, int.class), thisBinder.type()); assertEquals(MethodType.methodType(String.class, String.class, String.class), newBinder.type()); MethodHandle target = newBinder.invoke(Subjects.concatHandle()); assertEquals("Hello, world", (String)target.invokeExact()); } @Test public void testType() throws Throwable { Binder binder = Binder .from(String.class, String.class, Integer.class); assertEquals(MethodType.methodType(String.class, String.class, Integer.class), binder.type()); binder = binder .drop(1); assertEquals(MethodType.methodType(String.class, String.class), binder.type()); } @Test public void testPrintType() throws Throwable { Binder binder = Binder .from(String.class, String.class, Integer.class); ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(baos); binder.printType(ps); assertEquals("(String,Integer)String\n", baos.toString()); binder = binder .drop(1); baos = new ByteArrayOutputStream(); ps = new PrintStream(baos); binder.printType(ps); assertEquals("(String)String\n", baos.toString()); } @Test public void testInsert() throws Throwable { MethodHandle target = Subjects.concatHandle(); MethodHandle handle = Binder .from(String.class, String.class) .insert(1, "world") .invoke(target); assertEquals(MethodType.methodType(String.class, String.class), handle.type()); assertEquals("Hello, world", (String) handle.invokeExact("Hello, ")); MethodHandle target2 = Subjects.concatCharSequenceHandle(); MethodHandle handle2 = Binder .from(String.class, String.class) .insert(1, CharSequence.class, "world") .invoke(target2); assertEquals(MethodType.methodType(String.class, String.class), handle2.type()); assertEquals("Hello, world", (String) handle2.invokeExact("Hello, ")); } @Test public void testAppend() throws Throwable { MethodHandle target = Subjects.concatHandle(); MethodHandle handle = Binder .from(String.class, String.class, Object.class) .append("world") .drop(1) .invoke(target); assertEquals(MethodType.methodType(String.class, String.class, Object.class), handle.type()); assertEquals("Hello, world", (String) handle.invokeExact("Hello, ", new Object())); MethodHandle target2 = Subjects.concatCharSequenceHandle(); MethodHandle handle2 = Binder .from(String.class, String.class, Object.class) .append(CharSequence.class, "world") .drop(1) .invoke(target2); assertEquals(MethodType.methodType(String.class, String.class, Object.class), handle2.type()); assertEquals("Hello, world", (String) handle2.invokeExact("Hello, ", new Object())); } @Test public void testPrepend() throws Throwable { MethodHandle target = Subjects.concatHandle(); MethodHandle handle = Binder .from(String.class, Object.class, String.class) .prepend("Hello, ") .drop(1) .invoke(target); assertEquals(MethodType.methodType(String.class, Object.class, String.class), handle.type()); assertEquals("Hello, world", (String) handle.invokeExact(new Object(), "world")); MethodHandle target2 = Subjects.concatHandle(); MethodHandle handle2 = Binder .from(String.class, Object.class, String.class) .prepend(String.class, "Hello, ") .drop(1) .invoke(target2); assertEquals(MethodType.methodType(String.class, Object.class, String.class), handle2.type()); assertEquals("Hello, world", (String) handle2.invokeExact(new Object(), "world")); } @Test public void testDropInsert() throws Throwable { MethodHandle target = Subjects.concatHandle(); MethodHandle handle = Binder .from(String.class, String.class, Object.class) .drop(1) .insert(1, "world") .invoke(target); assertEquals(MethodType.methodType(String.class, String.class, Object.class), handle.type()); assertEquals("Hello, world", (String) handle.invokeExact("Hello, ", new Object())); } @Test public void testDropLast() throws Throwable { MethodHandle target = Subjects.concatHandle(); MethodHandle handle = Binder .from(String.class, String.class, Object.class) .dropLast() .insert(1, "world") .invoke(target); assertEquals(MethodType.methodType(String.class, String.class, Object.class), handle.type()); assertEquals("Hello, world", (String) handle.invokeExact("Hello, ", new Object())); handle = Binder .from(String.class, String.class, Object.class, double.class) .dropLast(2) .insert(1, "world") .invoke(target); assertEquals(MethodType.methodType(String.class, String.class, Object.class, double.class), handle.type()); assertEquals("Hello, world", (String) handle.invokeExact("Hello, ", new Object(), 1.0)); } @Test public void testDropFirst() throws Throwable { MethodHandle target = Subjects.concatHandle(); MethodHandle handle = Binder .from(String.class, Object.class, String.class) .dropFirst() .insert(1, "world") .invoke(target); assertEquals(MethodType.methodType(String.class, Object.class, String.class), handle.type()); assertEquals("Hello, world", (String) handle.invokeExact(new Object(), "Hello, ")); handle = Binder .from(String.class, Object.class, double.class, String.class) .dropFirst(2) .insert(1, "world") .invoke(target); assertEquals(MethodType.methodType(String.class, Object.class, double.class, String.class), handle.type()); assertEquals("Hello, world", (String) handle.invokeExact(new Object(), 1.0, "Hello, ")); } @Test public void testDropAll() throws Throwable { MethodHandle target = Subjects.concatHandle(); MethodHandle handle = Binder .from(String.class, String.class, Object.class) .dropAll() .insert(0, "Hello, ", "world") .invoke(target); assertEquals(MethodType.methodType(String.class, String.class, Object.class), handle.type()); assertEquals("Hello, world", (String) handle.invokeExact("Hello, ", new Object())); } @Test public void testConvert() throws Throwable { MethodHandle target = mixedHandle(); MethodHandle handle = Binder .from(String.class, Object.class, Integer.class, Float.class) .convert(target.type()) .invoke(target); assertEquals(MethodType.methodType(String.class, Object.class, Integer.class, Float.class), handle.type()); assertEquals(null, (String) handle.invokeExact((Object) "foo", (Integer) 5, (Float) 5.0f)); } @Test public void testConvert2() throws Throwable { MethodHandle target = mixedHandle(); MethodHandle handle = Binder .from(String.class, Object.class, Integer.class, Float.class) .convert(target.type().returnType(), target.type().parameterArray()) .invoke(target); assertEquals(MethodType.methodType(String.class, Object.class, Integer.class, Float.class), handle.type()); assertEquals(null, (String)handle.invokeExact((Object)"foo", (Integer)5, (Float)5.0f)); } @Test public void testCast() throws Throwable { MethodHandle target = mixedHandle(); MethodHandle handle = Binder .from(String.class, Object.class, byte.class, int.class) .cast(target.type()) .invoke(target); assertEquals(MethodType.methodType(String.class, Object.class, byte.class, int.class), handle.type()); assertEquals(null, (String)handle.invokeExact((Object)"foo", (byte)5, 5)); } @Test public void testCast2() throws Throwable { MethodHandle target = mixedHandle(); MethodHandle handle = Binder .from(String.class, Object.class, byte.class, int.class) .cast(target.type().returnType(), target.type().parameterArray()) .invoke(target); assertEquals(MethodType.methodType(String.class, Object.class, byte.class, int.class), handle.type()); assertEquals(null, (String)handle.invokeExact((Object)"foo", (byte)5, 5)); } @Test public void testDropReorder() throws Throwable { MethodHandle target = Subjects.concatHandle(); MethodHandle handle = Binder .from(String.class, Integer.class, Float.class, String.class) .drop(0, 2) .permute(0, 0) .invoke(target); assertEquals(MethodType.methodType(String.class, Integer.class, Float.class, String.class), handle.type()); assertEquals("foofoo", (String)handle.invokeExact((Integer) 0, (Float) 0.0f, "foo")); } @Test public void testSpread() throws Throwable { MethodHandle target = Subjects.concatHandle(); MethodHandle handle = Binder .from(String.class, Object[].class) .spread(String.class, String.class) .invoke(target); assertEquals(MethodType.methodType(String.class, Object[].class), handle.type()); assertEquals("foobar", (String)handle.invokeExact(new Object[] {"foo", "bar"})); } @Test public void testSpreadCount() throws Throwable { MethodHandle target = Subjects.concatHandle(); MethodHandle handle = Binder .from(String.class, String[].class) .spread(2) .invoke(target); assertEquals(MethodType.methodType(String.class, String[].class), handle.type()); assertEquals("foobar", (String)handle.invokeExact(new String[] {"foo", "bar"})); } @Test public void testCollect() throws Throwable { MethodHandle handle = Binder .from(String[].class, String.class, String.class, String.class) .collect(1, String[].class) .invokeStatic(LOOKUP, BinderTest.class, "varargs"); assertEquals(MethodType.methodType(String[].class, String.class, String.class, String.class), handle.type()); String[] ary = (String[])handle.invokeExact("one", "two", "three"); assertEquals(2, ary.length); assertEquals("two", ary[0]); assertEquals("three", ary[1]); MethodHandle handle2 = Binder .from(Subjects.StringIntegerIntegerIntegerString.type()) .collect(1, 3, Integer[].class) .invoke(Subjects.StringIntegersStringHandle); assertEquals(MethodType.methodType(String.class, String.class, Integer.class, Integer.class, Integer.class, String.class), handle2.type()); assertEquals("[foo, [1, 2, 3], bar]", (String)handle2.invokeExact("foo", new Integer(1), new Integer(2), new Integer(3), "bar")); } @Test public void testVarargs() throws Throwable { MethodHandle handle = Binder .from(String[].class, String.class, String.class, String.class) .varargs(1, String[].class) .invokeStatic(LOOKUP, BinderTest.class, "varargs"); assertEquals(MethodType.methodType(String[].class, String.class, String.class, String.class), handle.type()); String[] ary = (String[])handle.invokeExact("one", "two", "three"); assertEquals(2, ary.length); assertEquals("two", ary[0]); assertEquals("three", ary[1]); // from #2 MethodHandle foo = Binder.from(MethodType.methodType(String.class, String.class)) .varargs(0, Object[].class) .invokeStatic(MethodHandles.publicLookup(), getClass(), "varargs"); assertEquals(foo.invokeWithArguments("value"), "value"); } @Test public void testConstant() throws Throwable { MethodHandle handle = Binder .from(String.class) .constant("hello"); assertEquals(MethodType.methodType(String.class), handle.type()); assertEquals("hello", (String)handle.invokeExact()); } @Test public void testConstant2() throws Throwable { MethodHandle handle = Binder .from(Object.class) .constant("hello"); assertEquals(MethodType.methodType(Object.class), handle.type()); assertEquals("hello", (Object)handle.invokeExact()); } @Test public void testIdentity() throws Throwable { MethodHandle handle = Binder .from(String.class, String.class) .identity(); assertEquals(MethodType.methodType(String.class, String.class), handle.type()); assertEquals("hello", (String)handle.invokeExact("hello")); } @Test public void testFold() throws Throwable { MethodHandle target = Subjects.concatHandle(); MethodHandle fold = Binder .from(String.class, String.class) .drop(0) .constant("yahoo"); MethodHandle handle = Binder .from(String.class, String.class) .fold(fold) .invoke(target); assertEquals(MethodType.methodType(String.class, String.class), handle.type()); assertEquals("yahoofoo", (String)handle.invokeExact("foo")); } @Test public void testFoldStatic() throws Throwable { MethodHandle target = Subjects.concatHandle(); MethodHandle handle = Binder .from(LOOKUP, String.class, String.class) .foldStatic(BinderTest.class, "alwaysYahooStatic") .invoke(target); assertEquals(MethodType.methodType(String.class, String.class), handle.type()); assertEquals("yahoofoo", (String)handle.invokeExact("foo")); } @Test public void testFoldVirtual() throws Throwable { MethodHandle target = Subjects.concatHandle(); MethodHandle handle = Binder .from(LOOKUP, String.class, String.class) .insert(0, this) .foldVirtual("alwaysYahooVirtual") .drop(1) .invoke(target); assertEquals(MethodType.methodType(String.class, String.class), handle.type()); assertEquals("yahoofoo", (String)handle.invokeExact("foo")); } @Test public void testFilter() throws Throwable { MethodHandle target = Subjects.concatHandle(); MethodHandle filter = LOOKUP.findStatic(BinderTest.class, "addBaz", MethodType.methodType(String.class, String.class)); MethodHandle handle = Binder .from(String.class, String.class, String.class) .filter(0, filter, filter) .invoke(target); assertEquals(MethodType.methodType(String.class, String.class, String.class), handle.type()); assertEquals("foobazbarbaz", (String)handle.invokeExact("foo", "bar")); } @Test public void testInvoke() throws Throwable { MethodHandle target = Subjects.concatHandle(); MethodHandle handle = Binder .from(String.class, String.class, String.class) .invoke(target); assertEquals(MethodType.methodType(String.class, String.class, String.class), handle.type()); assertEquals("Hello, world", (String) handle.invokeExact("Hello, ", "world")); } @Test public void testInvokeReflected() throws Throwable { Method target = Subjects.class.getMethod("concatStatic", String.class, String.class); MethodHandle handle = Binder .from(String.class, String.class, String.class) .invoke(LOOKUP, target); assertEquals(MethodType.methodType(String.class, String.class, String.class), handle.type()); assertEquals("Hello, world", (String) handle.invokeExact("Hello, ", "world")); } @Test public void testInvokeReflected2() throws Throwable { Method target = Subjects.class.getMethod("concatStatic", String.class, String.class); MethodHandle handle = Binder .from(String.class, String.class, String.class) .invokeQuiet(LOOKUP, target); assertEquals(MethodType.methodType(String.class, String.class, String.class), handle.type()); assertEquals("Hello, world", (String) handle.invokeExact("Hello, ", "world")); } @Test public void testInvokeStatic() throws Throwable { MethodHandle handle = Binder .from(String.class, String.class, String.class) .invokeStatic(LOOKUP, Subjects.class, "concatStatic"); assertEquals(MethodType.methodType(String.class, String.class, String.class), handle.type()); assertEquals("Hello, world", (String) handle.invokeExact("Hello, ", "world")); } @Test public void testInvokeStatic2() throws Throwable { MethodHandle handle = Binder .from(String.class, String.class, String.class) .invokeStaticQuiet(LOOKUP, Subjects.class, "concatStatic"); assertEquals(MethodType.methodType(String.class, String.class, String.class), handle.type()); assertEquals("Hello, world", (String) handle.invokeExact("Hello, ", "world")); } @Test public void testInvokeVirtual() throws Throwable { MethodHandle handle = Binder .from(String.class, BinderTest.class, String.class, String.class) .invokeVirtual(LOOKUP, "concatVirtual"); assertEquals(MethodType.methodType(String.class, BinderTest.class, String.class, String.class), handle.type()); assertEquals("Hello, world", (String) handle.invokeExact(this, "Hello, ", "world")); } @Test public void testInvokeVirtual2() throws Throwable { MethodHandle handle = Binder .from(String.class, BinderTest.class, String.class, String.class) .invokeVirtualQuiet(LOOKUP, "concatVirtual"); assertEquals(MethodType.methodType(String.class, BinderTest.class, String.class, String.class), handle.type()); assertEquals("Hello, world", (String) handle.invokeExact(this, "Hello, ", "world")); } @Test public void testInvokeConstructor() throws Throwable { MethodHandle handle = Binder .from(Constructable.class, String.class, String.class) .invokeConstructor(LOOKUP, Constructable.class); assertEquals(MethodType.methodType(Constructable.class, String.class, String.class), handle.type()); assertEquals(new Constructable("foo", "bar"), (Constructable) handle.invokeExact("foo", "bar")); } @Test public void testInvokeConstructor2() throws Throwable { MethodHandle handle = Binder .from(Constructable.class, String.class, String.class) .invokeConstructorQuiet(LOOKUP, Constructable.class); assertEquals(MethodType.methodType(Constructable.class, String.class, String.class), handle.type()); assertEquals(new Constructable("foo", "bar"), (Constructable) handle.invokeExact("foo", "bar")); } @Test public void testGetField() throws Throwable { Fields fields = new Fields(); MethodHandle handle = Binder .from(String.class, Fields.class) .getField(LOOKUP, "instanceField"); assertEquals(MethodType.methodType(String.class, Fields.class), handle.type()); assertEquals("initial", (String)handle.invokeExact(fields)); } @Test public void testGetField2() throws Throwable { Fields fields = new Fields(); MethodHandle handle = Binder .from(String.class, Fields.class) .getFieldQuiet(LOOKUP, "instanceField"); assertEquals(MethodType.methodType(String.class, Fields.class), handle.type()); assertEquals("initial", (String)handle.invokeExact(fields)); } @Test public void testGetStatic() throws Throwable { MethodHandle handle = Binder .from(String.class) .getStatic(LOOKUP, Fields.class, "staticField"); assertEquals(MethodType.methodType(String.class), handle.type()); assertEquals("initial", (String)handle.invokeExact()); } @Test public void testGetStatic2() throws Throwable { MethodHandle handle = Binder .from(String.class) .getStaticQuiet(LOOKUP, Fields.class, "staticField"); assertEquals(MethodType.methodType(String.class), handle.type()); assertEquals("initial", (String)handle.invokeExact()); } @Test public void testSetField() throws Throwable { Fields fields = new Fields(); MethodHandle handle = Binder .from(void.class, Fields.class, String.class) .setField(LOOKUP, "instanceField"); assertEquals(MethodType.methodType(void.class, Fields.class, String.class), handle.type()); handle.invokeExact(fields, "modified"); assertEquals("modified", fields.instanceField); } @Test public void testSetField2() throws Throwable { Fields fields = new Fields(); MethodHandle handle = Binder .from(void.class, Fields.class, String.class) .setFieldQuiet(LOOKUP, "instanceField"); assertEquals(MethodType.methodType(void.class, Fields.class, String.class), handle.type()); handle.invokeExact(fields, "modified"); assertEquals("modified", fields.instanceField); } @Test public void testSetStatic() throws Throwable { try { MethodHandle handle = Binder .from(void.class, String.class) .setStatic(LOOKUP, Fields.class, "staticField"); assertEquals(MethodType.methodType(void.class, String.class), handle.type()); handle.invokeExact("modified"); assertEquals("modified", Fields.staticField); } finally { Fields.staticField = "initial"; } } @Test public void testSetStatic2() throws Throwable { try { MethodHandle handle = Binder .from(void.class, String.class) .setStaticQuiet(LOOKUP, Fields.class, "staticField"); assertEquals(MethodType.methodType(void.class, String.class), handle.type()); handle.invokeExact("modified"); assertEquals("modified", Fields.staticField); } finally { Fields.staticField = "initial"; } } @Test public void testNop() throws Throwable { MethodHandle handle = Binder .from(void.class, int.class, String.class) .nop(); assertEquals(MethodType.methodType(void.class, int.class, String.class), handle.type()); try { handle.invokeExact(1, "foo"); } catch (Throwable t) { assertTrue("should not reach here", false); } } @Test public void testThrowException() throws Throwable { MethodHandle handle = Binder .from(void.class, BlahException.class) .throwException(); assertEquals(MethodType.methodType(void.class, BlahException.class), handle.type()); try { handle.invokeExact(new BlahException()); assertTrue("should not reach here", false); } catch (BlahException be) { } } @Test public void testTryFinally() throws Throwable { MethodHandle post = Binder .from(void.class, String[].class) .invokeStatic(LOOKUP, BinderTest.class, "finallyLogic"); MethodHandle handle = Binder .from(void.class, String[].class) .tryFinally(post) .invokeStatic(LOOKUP, BinderTest.class, "setZeroToFoo"); assertEquals(MethodType.methodType(void.class, String[].class), handle.type()); String[] stringAry = new String[1]; handle.invokeExact(stringAry); assertEquals("foofinally", stringAry[0]); } @Test public void testTryFinally2() throws Throwable { MethodHandle post = Binder .from(void.class, String[].class) .invokeStatic(LOOKUP, BinderTest.class, "finallyLogic"); MethodHandle handle = Binder .from(void.class, String[].class) .tryFinally(post) .invokeStatic(LOOKUP, BinderTest.class, "setZeroToFooAndRaise"); assertEquals(MethodType.methodType(void.class, String[].class), handle.type()); String[] stringAry = new String[1]; try { handle.invokeExact(stringAry); assertTrue("should not have reached here", false); } catch (BlahException re) { } assertEquals("foofinally", stringAry[0]); } @Test public void testTryFinally3() throws Throwable { MethodHandle post = Binder .from(void.class, String[].class) .invokeStatic(LOOKUP, BinderTest.class, "finallyLogic"); MethodHandle ignoreException = Binder .from(void.class, BlahException.class, String[].class) .nop(); MethodHandle handle = Binder .from(void.class, String[].class) .tryFinally(post) .catchException(BlahException.class, ignoreException) .invokeStatic(LOOKUP, BinderTest.class, "setZeroToFooAndRaise"); assertEquals(MethodType.methodType(void.class, String[].class), handle.type()); String[] stringAry = new String[1]; try { handle.invokeExact(stringAry); } catch (BlahException re) { assertTrue("should not have reached here", false); } assertEquals("foofinally", stringAry[0]); } @Test public void testTryFinallyReturn() throws Throwable { MethodHandle post = Binder .from(void.class, String[].class) .invokeStatic(LOOKUP, BinderTest.class, "finallyLogic"); MethodHandle handle = Binder .from(int.class, String[].class) .tryFinally(post) .invokeStatic(LOOKUP, BinderTest.class, "setZeroToFooReturnInt"); assertEquals(MethodType.methodType(int.class, String[].class), handle.type()); String[] stringAry = new String[1]; assertEquals(1, (int)handle.invokeExact(stringAry)); assertEquals("foofinally", stringAry[0]); } @Test public void testTryFinallyReturn2() throws Throwable { MethodHandle post = Binder .from(void.class, String[].class) .invokeStatic(LOOKUP, BinderTest.class, "finallyLogic"); MethodHandle handle = Binder .from(int.class, String[].class) .tryFinally(post) .invokeStatic(LOOKUP, BinderTest.class, "setZeroToFooReturnIntAndRaise"); assertEquals(MethodType.methodType(int.class, String[].class), handle.type()); String[] stringAry = new String[1]; try { int x = (int)handle.invokeExact(stringAry); assertTrue("should not have reached here", false); } catch (BlahException re) { } assertEquals("foofinally", stringAry[0]); } @Test public void testTryFinallyReturn3() throws Throwable { MethodHandle post = Binder .from(void.class, String[].class) .invokeStatic(LOOKUP, BinderTest.class, "finallyLogic"); MethodHandle ignoreException = Binder .from(int.class, BlahException.class, String[].class) .drop(0, 2) .constant(1); MethodHandle handle = Binder .from(int.class, String[].class) .tryFinally(post) .catchException(BlahException.class, ignoreException) .invokeStatic(LOOKUP, BinderTest.class, "setZeroToFooReturnIntAndRaise"); assertEquals(MethodType.methodType(int.class, String[].class), handle.type()); String[] stringAry = new String[1]; try { assertEquals(1, (int)handle.invokeExact(stringAry)); } catch (BlahException be) { assertTrue("should not have reached here", false); } assertEquals("foofinally", stringAry[0]); } @Test public void testArraySet() throws Throwable { MethodHandle handle = Binder .from(void.class, Object[].class, int.class, Object.class) .arraySet(); assertEquals(MethodType.methodType(void.class, Object[].class, int.class, Object.class), handle.type()); Object[] ary = new Object[1]; handle.invokeExact(ary, 0, (Object)"foo"); assertEquals(ary[0], "foo"); } @Test public void testArrayGet() throws Throwable { MethodHandle handle = Binder .from(Object.class, Object[].class, int.class) .arrayGet(); assertEquals(MethodType.methodType(Object.class, Object[].class, int.class), handle.type()); Object[] ary = new Object[] {"foo"}; assertEquals(handle.invokeExact(ary, 0), "foo"); } @Test public void testBranch() throws Throwable { MethodHandle handle = Binder .from(String.class, String.class) .branch( Binder .from(boolean.class, String.class) .invokeStatic(LOOKUP, BinderTest.class, "isStringFoo"), Binder .from(String.class, String.class) .invokeStatic(LOOKUP, BinderTest.class, "addBar"), Binder .from(String.class, String.class) .invokeStatic(LOOKUP, BinderTest.class, "addBaz") ); assertEquals(MethodType.methodType(String.class, String.class), handle.type()); assertEquals("foobar", (String)handle.invokeExact("foo")); assertEquals("quuxbaz", (String)handle.invokeExact("quux")); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////// public static MethodHandle intLongHandle() throws Exception { return LOOKUP.findStatic(BinderTest.class, "intLong", MethodType.methodType(String.class, int.class, long.class)); } public String concatVirtual(String a, String b) { return a + b; } public static boolean isStringFoo(String a) { return a.equals("foo"); } public static String addBar(String a) { return a + "bar"; } public static String addBaz(String a) { return a + "baz"; } public static void setZeroToFoo(String[] ary) { ary[0] = "foo"; } public static void setZeroToFooAndRaise(String[] ary) throws BlahException { ary[0] = "foo"; throw new BlahException(); } public static int setZeroToFooReturnInt(String[] ary) { ary[0] = "foo"; return 1; } public static int setZeroToFooReturnIntAndRaise(String[] ary) throws BlahException { ary[0] = "foo"; throw new BlahException(); } public static void finallyLogic(String[] ary) { ary[0] = ary[0] + "finally"; } public static String[] varargs(String arg0, String... args) { return args; } public static String varargs(Object... args) { return (String) args[0]; } public static String intLong(int a, long b) { return "intLong ok"; } public static class BlahException extends Exception {} public static class Fields { public String instanceField = "initial"; public static String staticField = "initial"; } /** * Represents a constructable object that's always equal to other constructables. */ public static class Constructable { private final String a, b; public Constructable(String a, String b) { this.a = a; this.b = b; } public boolean equals(Object other) { if (!(other instanceof Constructable)) return false; Constructable c = (Constructable)other; return a.equals(c.a) && b.equals(c.b); } } public static MethodHandle mixedHandle() throws Exception { return LOOKUP.findStatic(BinderTest.class, "mixed", MethodType.methodType(void.class, String.class, int.class, float.class)); } public static void mixed(String a, int b, float c) { } public static String alwaysYahooStatic(String ignored) { return "yahoo"; } public String alwaysYahooVirtual(String ignored) { return "yahoo"; } } invokebinder-invokebinder-1.7/src/test/java/com/headius/invokebinder/SignatureTest.java000066400000000000000000000310651264574172000315440ustar00rootroot00000000000000/* * Copyright 2013 headius. * * 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 com.headius.invokebinder; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.util.List; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.*; /** * * @author headius */ public class SignatureTest { public SignatureTest() { } @BeforeClass public static void setUpClass() { } @AfterClass public static void tearDownClass() { } @Before public void setUp() { } @After public void tearDown() { } /** * Test of toString method, of class Signature. */ @Test public void testToString() { assertEquals("(Object obj, int num)String", stringObjectInt.toString()); } /** * Test of returning method, of class Signature. */ @Test public void testReturning() { Signature sig = Signature.returning(String.class); assertEquals(String.class, sig.type().returnType()); } /** * Test of asFold method, of class Signature. */ @Test public void testAsFold() { Signature sig = Signature .returning(String.class) .asFold(Object.class); assertEquals(Object.class, sig.type().returnType()); } /** * Test of appendArg method, of class Signature. */ @Test public void testAppendArg() { Signature sig = Signature .returning(String.class) .appendArg("obj", Object.class); assertEquals("(Object obj)String", sig.toString()); sig = sig .appendArg("num", int.class); assertEquals("(Object obj, int num)String", sig.toString()); } /** * Test of prependArg method, of class Signature. */ @Test public void testPrependArg() { Signature sig = Signature .returning(String.class) .appendArg("num", int.class); assertEquals("(int num)String", sig.toString()); sig = sig .prependArg("obj", Object.class); assertEquals("(Object obj, int num)String", sig.toString()); } /** * Test of insertArg method, of class Signature. */ @Test public void testInsertArg() { Signature sig = Signature .returning(String.class) .appendArg("obj", Object.class) .appendArg("num", int.class) .insertArg(1, "flo", float.class); assertEquals("(Object obj, float flo, int num)String", sig.toString()); } @Test public void testInsertArgBefore() { Signature sig = Signature .returning(String.class) .appendArg("obj", Object.class) .appendArg("num", int.class) .insertArg("num", "flo", float.class); assertEquals("(Object obj, float flo, int num)String", sig.toString()); } /** * Test of insertArgs method, of class Signature. */ @Test public void testInsertArgs() { Signature sig = Signature .returning(String.class) .appendArg("obj", Object.class) .appendArg("num", int.class) .insertArgs(1, new String[]{"flo", "dub"}, new Class[] {float.class, double.class}); assertEquals("(Object obj, float flo, double dub, int num)String", sig.toString()); } @Test public void testInsertArgsBefore() { Signature sig = Signature .returning(String.class) .appendArg("obj", Object.class) .appendArg("num", int.class) .insertArgs("num", new String[]{"flo", "dub"}, new Class[] {float.class, double.class}); assertEquals("(Object obj, float flo, double dub, int num)String", sig.toString()); } /** * Test of methodType method, of class Signature. */ @Test public void testMethodType() { assertEquals(MethodType.methodType(String.class, Object.class, int.class), stringObjectInt.type()); } /** * Test of argNames method, of class Signature. */ @Test public void testArgNames() { assertArrayEquals(new String[] {"obj", "num"}, stringObjectInt.argNames()); } @Test public void testArgName() { assertEquals("num", stringObjectInt.appendArg("flo", float.class).argName(1)); Signature oldSig = stringObjectInt; Signature newSig = oldSig.argName(0, "str"); assertEquals("str", newSig.argName(0)); } @Test public void testLastArgName() { assertEquals("flo", stringObjectInt.appendArg("flo", float.class).lastArgName()); } @Test public void testFirstArgName() { assertEquals("obj", stringObjectInt.appendArg("flo", float.class).firstArgName()); } @Test public void testArgOffset() { assertEquals(1, stringObjectInt.argOffset("num")); assertEquals(-1, stringObjectInt.argOffset("flo")); } @Test public void testArgOffsets() { assertEquals(1, stringObjectInt.argOffsets("nu*")); assertEquals(-1, stringObjectInt.argOffsets("fl.")); } @Test public void testArgType() { assertEquals(int.class, stringObjectInt.appendArg("flo", float.class).argType(1)); Signature oldSig = stringObjectInt; Signature newSig = oldSig.argType(0, String.class); assertEquals(String.class, newSig.argType(0)); } @Test public void testFirstArgType() { assertEquals(Object.class, stringObjectInt.appendArg("flo", float.class).firstArgType()); } @Test public void testLastArgType() { assertEquals(float.class, stringObjectInt.appendArg("flo", float.class).lastArgType()); } /** * Test of permute method, of class Signature. */ @Test public void testPermute() { Signature sig = stringObjectInt .appendArg("flo", float.class) .appendArg("dub", double.class) .permute("obj", "dub"); assertEquals("(Object obj, double dub)String", sig.toString()); } @Test public void testExclude() { Signature sig = stringObjectInt .appendArg("flo", float.class) .appendArg("dub", double.class) .exclude("obj", "dub"); assertEquals("(int num, float flo)String", sig.toString()); } /** * Test of permuteTo method, of class Signature. */ @Test public void testPermuteWith() throws Throwable { MethodHandle handle = stringObjectInt .appendArg("flo", float.class) .appendArg("dub", double.class) .permuteWith(stringObjectIntTarget, "obj", "num"); assertEquals(MethodType.methodType(String.class, Object.class, int.class, float.class, double.class), handle.type()); assertEquals("foo1", (String)handle.invokeExact((Object)"foo", 1, 1.0f, 1.0)); } @Test public void testPermuteWithSmartHandle() throws Throwable { SmartHandle target = new SmartHandle(stringObjectInt, stringObjectIntTarget); SmartHandle permuted = stringObjectInt .appendArg("flo", float.class) .appendArg("dub", double.class) .permuteWith(target); assertEquals("(Object obj, int num, float flo, double dub)String", permuted.signature().toString()); assertEquals("foo1", permuted.handle().invokeWithArguments("foo", 1, 1.0f, 1.0)); } @Test public void testSpreadNamesAndTypes() throws Throwable { Signature sig = stringObjectAry .spread(new String[]{"int", "flo"}, Integer.class, Float.class); assertEquals("(Integer int, Float flo)String", sig.toString()); } @Test public void testSpreadNames() throws Throwable { Signature sig = stringObjectAry .spread("obj0", "obj1"); assertEquals("(Object obj0, Object obj1)String", sig.toString()); } @Test public void testSpreadNameAndCount() throws Throwable { Signature sig = stringObjectAry .spread("obj", 2); assertEquals("(Object obj0, Object obj1)String", sig.toString()); } /** * Test of to method, of class Signature. */ @Test public void testTo_Signature() { int[] permuteInts = stringObjectInt .appendArg("flo", float.class) .appendArg("dub", double.class) .to(stringObjectInt); assertArrayEquals(new int[] {0, 1}, permuteInts); } /** * Test of to method, of class Signature. */ @Test public void testTo_StringArr() { int[] permuteInts = stringObjectInt .appendArg("flo", float.class) .appendArg("dub", double.class) .to(".*o.*"); assertArrayEquals(new int[] {0, 2}, permuteInts); permuteInts = stringObjectInt .appendArg("flo", float.class) .appendArg("dub", double.class) .to("num", "dub"); assertArrayEquals(new int[] {1, 3}, permuteInts); } @Test public void testDropArg() { Signature newSig = stringObjectInt .appendArg("flo", float.class) .dropArg("num"); assertEquals("(Object obj, float flo)String", newSig.toString()); } @Test public void testDropArgIndex() { Signature newSig = stringObjectInt .appendArg("flo", float.class) .dropArg(1); assertEquals("(Object obj, float flo)String", newSig.toString()); } @Test public void testDropLast() { Signature newSig = stringObjectInt .dropLast(); assertEquals("(Object obj)String", newSig.toString()); newSig = stringObjectInt .dropLast(2); assertEquals("()String", newSig.toString()); } @Test public void testDropFirst() { Signature newSig = stringObjectInt .dropFirst(); assertEquals("(int num)String", newSig.toString()); newSig = stringObjectInt .dropFirst(2); assertEquals("()String", newSig.toString()); } @Test public void testReplaceArg() { Signature newSig = stringObjectInt .replaceArg("obj", "list", List.class); assertEquals("(List list, int num)String", newSig.toString()); } @Test public void testCollect() { Signature oldSig = Subjects.StringIntegerIntegerIntegerString; Signature newSig = oldSig.collect("bs", "b.*"); assertEquals(Integer[].class, newSig.argType(1)); assertEquals("bs", newSig.argName(1)); assertEquals(3, newSig.argCount()); assertEquals("c", newSig.argName(2)); oldSig = Subjects.StringIntegerIntegerInteger; newSig = oldSig.collect("bs", "b.*"); assertEquals(Integer[].class, newSig.argType(1)); assertEquals("bs", newSig.argName(1)); assertEquals(2, newSig.argCount()); } private static final Signature stringObjectInt = Signature .returning(String.class) .appendArg("obj", Object.class) .appendArg("num", int.class); private static final Signature stringObjectAry = Signature .returning(String.class) .appendArg("objs", Object[].class); private static final MethodHandle stringObjectIntTarget = Binder .from(String.class, Object.class, int.class) .invokeStaticQuiet(MethodHandles.lookup(), SignatureTest.class, "stringObjectIntMethod"); private static String stringObjectIntMethod(Object obj, int num) { return obj.toString() + num; } } invokebinder-invokebinder-1.7/src/test/java/com/headius/invokebinder/SmartBinderTest.java000066400000000000000000000333651264574172000320220ustar00rootroot00000000000000/* * Copyright 2013 headius. * * 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 com.headius.invokebinder; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.*; /** * * @author headius */ public class SmartBinderTest { public SmartBinderTest() { } @BeforeClass public static void setUpClass() { } @AfterClass public static void tearDownClass() { } @Before public void setUp() { } @After public void tearDown() { } /** * Test of from method, of class SmartBinder. */ @Test public void testFrom_Signature() throws Throwable { SmartHandle handle = SmartBinder .from(Signature.returning(String.class).appendArg("int", int.class)) .invoke(stringInt); assertEquals("42", (String)handle.handle().invokeExact(42)); } @Test public void testDrop() throws Throwable { Signature oldSig = Subjects.StringIntegerIntegerIntegerString; SmartHandle handle = SmartBinder.from(oldSig) .drop("b1") .drop("b2") .drop("b3") .insert(1, "bs", new Integer[]{1, 2, 3}) .invoke(Subjects.StringIntegersStringHandle); assertEquals("[foo, [1, 2, 3], bar]", (String)handle.handle().invokeExact("foo", new Integer(5), new Integer(6), new Integer(7), "bar")); } @Test public void testDropLast() throws Throwable { Signature oldSig = Subjects.StringIntegerIntegerIntegerString; SmartHandle handle = SmartBinder.from(oldSig) .dropLast(4) .append("bs", new Integer[]{1, 2, 3}) .append("c", "baz") .invoke(Subjects.StringIntegersStringHandle); assertEquals("[foo, [1, 2, 3], baz]", (String)handle.handle().invokeExact("foo", new Integer(5), new Integer(6), new Integer(7), "bar")); } @Test public void testDropFirst() throws Throwable { Signature oldSig = Subjects.StringIntegerIntegerIntegerString; SmartHandle handle = SmartBinder.from(oldSig) .dropFirst(4) .prepend("bs", new Integer[]{1, 2, 3}) .prepend("a", "baz") .invoke(Subjects.StringIntegersStringHandle); assertEquals("[baz, [1, 2, 3], bar]", (String)handle.handle().invokeExact("foo", new Integer(5), new Integer(6), new Integer(7), "bar")); } @Test public void testCollect() throws Throwable { Signature oldSig = Subjects.StringIntegerIntegerIntegerString; SmartHandle handle = SmartBinder .from(oldSig) .collect("bs", "b.*") .invoke(Subjects.StringIntegersStringHandle); assertEquals("[foo, [1, 2, 3], bar]", (String)handle.handle().invokeExact("foo", new Integer(1), new Integer(2), new Integer(3), "bar")); } @Test public void testInvokeStatic() throws Throwable { SmartHandle handle = SmartBinder .from(Subjects.StringIntegersString) .invokeStatic(LOOKUP, Subjects.class, "stringIntegersString"); assertEquals(Subjects.StringIntegersString, handle.signature()); assertEquals(Subjects.StringIntegersString.type(), handle.handle().type()); assertEquals("[foo, [1, 2, 3], bar]", (String)handle.handle().invokeExact("foo", new Integer[]{1,2,3}, "bar")); handle = SmartBinder .from(Subjects.StringIntegersString) .invokeStatic(LOOKUP, Subjects.class, "stringIntegersString"); assertEquals(Subjects.StringIntegersString, handle.signature()); assertEquals(Subjects.StringIntegersString.type(), handle.handle().type()); assertEquals("[foo, [1, 2, 3], bar]", (String)handle.handle().invokeExact("foo", new Integer[]{1,2,3}, "bar")); } @Test public void testInvokeVirtual() throws Throwable { Subjects subjects = new Subjects(); Signature thisSig = Subjects.StringIntegersString.prependArg("this", Subjects.class); SmartHandle handle = SmartBinder .from(thisSig) .invokeVirtual(LOOKUP, "stringIntegersString2"); assertEquals(thisSig, handle.signature()); assertEquals(thisSig.type(), handle.handle().type()); assertEquals("[foo, [1, 2, 3], bar]", (String)handle.handle().invokeExact(subjects, "foo", new Integer[]{1,2,3}, "bar")); handle = SmartBinder .from(thisSig) .invokeVirtual(LOOKUP, "stringIntegersString2"); assertEquals(thisSig, handle.signature()); assertEquals(thisSig.type(), handle.handle().type()); assertEquals("[foo, [1, 2, 3], bar]", (String)handle.handle().invokeExact(subjects, "foo", new Integer[]{1,2,3}, "bar")); } @Test public void testInsert() throws Throwable { MethodHandle target = Subjects.concatHandle(); SmartHandle handle = SmartBinder .from(String.class, "arg0", String.class) .insert(1, "arg1", "world") .invoke(target); assertEquals(MethodType.methodType(String.class, String.class), handle.signature().type()); assertEquals("Hello, world", (String) handle.handle().invokeExact("Hello, ")); MethodHandle target2 = Subjects.concatCharSequenceHandle(); SmartHandle handle2 = SmartBinder .from(String.class, "arg0", String.class) .insert(1, "arg1", CharSequence.class, "world") .invoke(target2); assertEquals(MethodType.methodType(String.class, String.class), handle2.signature().type()); assertEquals("Hello, world", (String) handle2.handle().invokeExact("Hello, ")); } @Test public void testAppend() throws Throwable { MethodHandle target = Subjects.concatHandle(); SmartHandle handle = SmartBinder .from(String.class, new String[] {"arg0", "arg1"}, String.class, Object.class) .append("arg2", "world") .drop("arg1") .invoke(target); assertEquals(MethodType.methodType(String.class, String.class, Object.class), handle.signature().type()); assertEquals("Hello, world", (String) handle.handle().invokeExact("Hello, ", new Object())); MethodHandle target2 = Subjects.concatCharSequenceHandle(); SmartHandle handle2 = SmartBinder .from(String.class, new String[] {"arg0", "arg1"}, String.class, Object.class) .append("arg2", CharSequence.class, "world") .drop("arg1") .invoke(target2); assertEquals(MethodType.methodType(String.class, String.class, Object.class), handle2.signature().type()); assertEquals("Hello, world", (String) handle2.handle().invokeExact("Hello, ", new Object())); } @Test public void testPrepend() throws Throwable { MethodHandle target = Subjects.concatHandle(); SmartHandle handle = SmartBinder .from(String.class, new String[]{"arg1", "arg2"}, Object.class, String.class) .prepend("arg0", "Hello, ") .drop("arg1") .invoke(target); assertEquals(MethodType.methodType(String.class, Object.class, String.class), handle.signature().type()); assertEquals("Hello, world", (String) handle.handle().invokeExact(new Object(), "world")); MethodHandle target2 = Subjects.concatHandle(); SmartHandle handle2 = SmartBinder .from(String.class, new String[] {"arg1", "arg2"}, Object.class, String.class) .prepend("arg0", String.class, "Hello, ") .drop("arg1") .invoke(target2); assertEquals(MethodType.methodType(String.class, Object.class, String.class), handle2.signature().type()); assertEquals("Hello, world", (String) handle2.handle().invokeExact(new Object(), "world")); } @Test public void testCast() throws Throwable { MethodHandle target = LOOKUP.unreflect(String.class.getMethod("split", String.class)); // cast for virtual call using full static signature SmartHandle handle = SmartBinder .from(Object.class, new String[]{"this", "regex"}, Object.class, Object.class) .cast(String[].class, String.class, String.class) .invoke(target); assertArrayEquals(new String[]{"foo", "bar"}, (String[])(Object)handle.handle().invokeExact((Object)"foo,bar", (Object)",")); // cast for virtual call using ret, this, args handle = SmartBinder .from(new Signature(Object.class, Object.class, new Class[]{Object.class}, "this", "regex")) .castVirtual(String[].class, String.class, new Class[]{String.class}) .invoke(target); assertArrayEquals(new String[]{"foo", "bar"}, (String[])(Object)handle.handle().invokeExact((Object)"foo,bar", (Object)",")); } @Test public void testFilter() throws Throwable { MethodHandle target = Subjects.concatHandle(); MethodHandle filter = MethodHandles.insertArguments(Subjects.concatHandle(), 1, "goodbye"); MethodHandle handle = SmartBinder .from(String.class, new String[]{"arg1", "arg2"}, String.class, String.class) .filter("arg.*", filter) .invoke(target).handle(); assertEquals(MethodType.methodType(String.class, String.class, String.class), handle.type()); assertEquals("foogoodbyebargoodbye", (String)handle.invokeExact("foo", "bar")); } @Test public void testIdentity() throws Throwable { MethodHandle handle = SmartBinder .from(String.class, "i", int.class) .fold("s", stringInt) .dropLast() .identity() .handle(); assertEquals(MethodType.methodType(String.class, int.class), handle.type()); assertEquals("15", (String)handle.invokeExact(15)); } @Test public void testFoldStatic() throws Throwable { MethodHandle handle = SmartBinder .from(Subjects.StringIntegerIntegerInteger) .foldStatic("x", Subjects.class, "foldStringIntegerIntegerInteger") .drop("a") .drop("b1") .drop("b2") .drop("b3") .invokeStatic(MethodHandles.lookup(), Subjects.class, "upperCase") .handle(); assertEquals("FORTY_TWO", (String)handle.invokeExact("foo", Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3))); } @Test public void testFoldVirtual() throws Throwable { MethodHandle handle = SmartBinder .from(Subjects.StringIntegerIntegerInteger) .prepend("this", new Subjects()) .foldVirtual("x", "foldVirtualStringIntegerIntegerInteger") .drop("this") .drop("a") .drop("b1") .drop("b2") .drop("b3") .invokeStatic(MethodHandles.lookup(), Subjects.class, "upperCase") .handle(); assertEquals("FORTY_TWO", (String)handle.invokeExact("foo", Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3))); } @Test public void testFoldVirtualWithLookup() throws Throwable { MethodHandle handle = SmartBinder .from(Subjects.StringIntegerIntegerInteger) .prepend("this", new Subjects()) .foldVirtual("x", MethodHandles.lookup(), "foldVirtualStringIntegerIntegerInteger") .drop("this") .drop("a") .drop("b1") .drop("b2") .drop("b3") .invokeStatic(MethodHandles.lookup(), Subjects.class, "upperCase") .handle(); assertEquals("FORTY_TWO", (String)handle.invokeExact("foo", Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3))); } @Test public void testPrintSignature() throws Throwable { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(baos); MethodHandle handle = SmartBinder .from(Subjects.StringIntegerIntegerInteger) .foldStatic("x", Subjects.class, "foldStringIntegerIntegerInteger") .drop("a").printSignature(ps) .drop("b1") .drop("b2") .drop("b3").printSignature(ps) .invokeStatic(MethodHandles.lookup(), Subjects.class, "upperCase") .handle(); String result = baos.toString(); assertEquals( "(String x, Integer b1, Integer b2, Integer b3)String\n" + "(String x)String\n", result); } private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); private static final MethodHandle stringInt = Binder .from(String.class, int.class) .invokeStaticQuiet(LOOKUP, Integer.class, "toString"); }invokebinder-invokebinder-1.7/src/test/java/com/headius/invokebinder/Subjects.java000066400000000000000000000057351264574172000305320ustar00rootroot00000000000000package com.headius.invokebinder; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.util.Arrays; /** * Created by headius on 1/25/14. */ public class Subjects { private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); public static final Signature StringIntegerIntegerIntegerString = Signature .returning(String.class) .appendArg("a", String.class) .appendArg("b1", Integer.class) .appendArg("b2", Integer.class) .appendArg("b3", Integer.class) .appendArg("c", String.class); public static final Signature StringIntegerIntegerInteger = Signature .returning(String.class) .appendArg("a", String.class) .appendArg("b1", Integer.class) .appendArg("b2", Integer.class) .appendArg("b3", Integer.class); public static final Signature StringIntegersString = Signature .returning(String.class) .appendArg("a", String.class) .appendArg("bs", Integer[].class) .appendArg("c", String.class); public static final MethodHandle StringIntegersStringHandle = Binder .from(String.class, String.class, Integer[].class, String.class) .invokeStaticQuiet(LOOKUP, Subjects.class, "stringIntegersString"); public static final MethodHandle StringIntegersHandle = Binder .from(String.class, String.class, Integer[].class) .invokeStaticQuiet(LOOKUP, Subjects.class, "stringIntegers"); public static String stringIntegersString(String a, Integer[] bs, String c) { return Arrays.deepToString(new Object[]{a, bs, c}); } public static String stringIntegers(String a, Integer[] bs) { return Arrays.deepToString(new Object[]{a, bs}); } public static MethodHandle concatHandle() throws Exception { return LOOKUP.findStatic(Subjects.class, "concatStatic", MethodType.methodType(String.class, String.class, String.class)); } public static MethodHandle concatCharSequenceHandle() throws Exception { return LOOKUP.findStatic(Subjects.class, "concatStatic", MethodType.methodType(String.class, String.class, CharSequence.class)); } public static String concatStatic(String a, String b) { return a + b; } public static String concatStatic(String a, CharSequence b) { return a + b; } public String stringIntegersString2(String a, Integer[] bs, String c) { return Arrays.deepToString(new Object[]{a, bs, c}); } public static String foldStringIntegerIntegerInteger(String a, Integer b1, Integer b2, Integer b3) { return "forty_two"; } public String foldVirtualStringIntegerIntegerInteger(String a, Integer b1, Integer b2, Integer b3) { return "forty_two"; } public static String upperCase(String x) { return x.toUpperCase(); } }