Move tag from exec to jexl where it belongs
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/jexl/tags/COMMONS_JEXL_2_0@1351799 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/COMMONS_JEXL_2_0/LICENSE.txt b/COMMONS_JEXL_2_0/LICENSE.txt
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/COMMONS_JEXL_2_0/LICENSE.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/COMMONS_JEXL_2_0/NOTICE.txt b/COMMONS_JEXL_2_0/NOTICE.txt
new file mode 100644
index 0000000..83b2c85
--- /dev/null
+++ b/COMMONS_JEXL_2_0/NOTICE.txt
@@ -0,0 +1,5 @@
+Apache Commons JEXL
+Copyright 2001-2010 The Apache Software Foundation
+
+This product includes software developed by
+The Apache Software Foundation (http://www.apache.org/).
diff --git a/COMMONS_JEXL_2_0/PROPOSAL.html b/COMMONS_JEXL_2_0/PROPOSAL.html
new file mode 100644
index 0000000..0f7ba1a
--- /dev/null
+++ b/COMMONS_JEXL_2_0/PROPOSAL.html
@@ -0,0 +1,90 @@
+<html>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+-->
+<head>
+<title>Proposal for Jexl Package</title>
+</head>
+<body bgcolor="white">
+
+<div align="center">
+<h1>Proposal for <em>Jexl</em> Package</h1>
+</div>
+
+<h3>(0) Rationale</h3>
+
+<p>The <em>Jexl</em> package implements a simple expression language for
+accessing Java objects.
+</p>
+
+<h3>(1) Scope of the Package</h3>
+<p>
+ The package will create and maintain a number of classes for
+ implementing a simple expression language and processing engine,
+ and to be distributed under the ASF license.
+</p>
+
+<h3>(1.5) Interaction With Other Packages</h3>
+
+<p><em>Jexl</em> relies on standard JDK 1.2 (or later) APIs for
+production deployment. </p>
+
+<p>
+<i>Jexl</i> utilizes the JUnit unit testing framework for developing and
+executing unit tests, but this is of interest only to developers of the
+component.
+</p>
+
+<p>
+<i>Jexl</i> also depends on Jakarta Velocity, Commons Logging, dom4j
+and Velocity DVSL for documentation rendering.
+</p>
+
+<h3>(2) Initial Source of the Package</h3>
+
+<p>
+ The code base is new and uses ideas from Jakarta Velocity.
+</p>
+
+<p>The proposed package name for the new component is
+<code>org.apache.commons.jexl</code>.</p>
+
+
+<h3>(3) Required Jakarta-Commons Resources</h3>
+
+<ul>
+<li>CVS Repository - New directory <code>jexl</code> in the
+ <code>jakarta-commons</code> CVS repository. All initial committers
+ are already committers on <code>jakarta-commons</code>, so no
+ additional user setups are required.</li>
+<li>Mailing List - Discussions will take place on the general
+ <em>jakarta-commons@jakarta.apache.org</em> mailing list. To help
+ list subscribers identify messages of interest, it is suggested that
+ the message subject of messages about this component be prefixed with
+ [Jexl]. Strongly suggested.</li>
+<li>Bugzilla - New component "Jexl" under the "Commons" product
+ category, with appropriate version identifiers as needed.</li>
+<li>Jyve FAQ - New category "commons-jexl" (when available). </li>
+</ul>
+
+
+<h3>(4) Initial Committers</h3>
+<ul>
+ <li>Geir Magnusson Jr.</li>
+ <li>James Strachan</li>
+</ul>
+</body>
+</html>
diff --git a/COMMONS_JEXL_2_0/RELEASE-NOTES.txt b/COMMONS_JEXL_2_0/RELEASE-NOTES.txt
new file mode 100644
index 0000000..1462d23
--- /dev/null
+++ b/COMMONS_JEXL_2_0/RELEASE-NOTES.txt
@@ -0,0 +1,176 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+-->
+$Id$
+
+ Commons JEXL Package
+ Version 2.0
+ Release Notes
+
+
+INTRODUCTION:
+=============
+
+JEXL is an Expression Language supporting most of the constructs in the
+JSTL Expression Language, along with some additional extensions.
+
+ http://commons.apache.org/jexl/
+
+Changes in this version include:
+
+Incompatible Changes
+====================
+
+Now requires Java 1.5 or later.
+
+Version 2.0 resides in the org.apache.commons.jexl2 package; part of the version 1.x API is reimplemented as an
+add-on source library in the jexl-compat directory; since it can not fully reimplement the original public 1.x, it may
+only be used to ease transition in strictly controlled deployments.
+
+The following classes are implemented through the jexl-compat source library:
+ * ExpressionFactory
+ * ScriptFactory
+ * Expression
+ * Script
+ * JexlContext
+ * JexlHelper
+
+Migration notes
+===============
+
+When migrating from jexl 1.x to jexl 2.0, the following hints may be helpfull.
+
+The following classes no longer exist:
+ * ExpressionFactory, ScriptFactory: create a JexlEngine and use createExpression() or createScript()
+ instead.
+
+The following classes have been renamed and replaced:
+
+ * VelMethod <=> JexlMethod
+ * VelPropertyGet <=> JexlPropertyGet
+ * VelPropertySet <=> JexlPropertySet
+
+The following methods have been removed:
+
+ * Info.getTemplateName() - use Info.getName() instead
+ * Expression.addPostResolver() / Expression.addPreResolver() - set ant-like variables in JexlContext, implement
+ a specific JexlContext or derive JexlcontextInterpreter/JexlEngine instead
+
+Behavior changes
+================
+
+* Public fields are considered when using JexlPropertyGet / JexlPropertySet: Jexl 1.x behavior can be reimplemented
+by subclassing UberspectImpl.
+
+*Division (/ operator) behavior change: division between integers no longer casts its operands to double; integer division
+ allways results in a integer. The 1.x behavior can be reimplemented by subclassing JexlArithmetic.
+
+New Features:
+=============
+
+Assignment expression: a = b (and a.b.c = d)
+ * Assigns a variable (ant-like variable or bean-property)
+
+Ternary operator expression: a ? b : c (and a ?: c)
+ * The usual inline conditional shortcut and its 'Elvis' form (a ?: b evaluates as a ? a : b)
+
+Constructor call expression: new('my.class.name', arguments...)
+ * Creates a new instance of a class using the most appropriate constructor according
+ to the actual arguments
+
+Function namespace: ns:func(arguments...)
+ * A function namespace allows the use of class or instance methods in function calls
+
+UnifiedJEXL
+ * Adds ${...} and #{...} JSP/EL syntax support on top of the JexlEngine
+
+JSR-223 support
+ * Implement JSR-223 Scripting Engine for JEXL script (need BSF-3.0 on Java < 6)
+
+Error and exception handling
+ * Configuring the leniency and verbosity of the Jexl engine allows user control over the
+ error handling policy
+
+Bugs fixed:
+===========
+
+* JEXL-90: Jexl parser allows invalid expressions, e.g. "a=1 b=2 3"
+* JEXL-88: MethodKey.java - name clash getMostSpecific() with Java 1.5.0
+* JEXL-87: Inconsistent behaviour of arithmetical operations
+* JEXL-81: Introspector does not use ListGetExecutor for List
+* JEXL-80: Lenient mode should not throw exception when {g,s}etting an undefined property
+* JEXL-78: Ternary operator throws Exception when JexlEngine in strict mode
+* JEXL-76: Remove unnecessary class VisitorAdapter
+* JEXL-71: Parsing errors?
+* JEXL-67: Potential NPE in util.introspection.MethodKey
+* JEXL-66: testDottedNames expects map enumeration order
+* JEXL-64: Inconsistent behaviour of dotted names
+* JEXL-62: NPE in Interpreter
+* JEXL-59: ClassMap holds a reference to class
+* JEXL-56: Logging wrongly uses java.util.logging
+* JEXL-50: Div operator does not do integer division
+* JEXL-49: Block statements aren't parsed
+* JEXL-48: NPE during expression evaluation
+* JEXL-45: Unhandled division by zero
+* JEXL-42: NullPointerException evaluating an expression
+* JEXL-40: JEXL fails to find abstract public methods in the base class if overridden by non-public derived types
+* JEXL-32: BigDecimal values are treated as Long values which results in loss of precision
+* JEXL-30: ASTAddNode does not add BigDecimal objects correctly
+* JEXL-27: Cannot assign a value to the property of an object, such as object.prop = value.
+* JEXL-26: ASTArrayAccess messes up on fallback to JexlContext
+* JEXL-19: Ternary conditional not supported
+* JEXL-3 : Static method resolution and changes to context
+
+Other issues fixed (Improvements/New Features):
+===============================================
+
+* JEXL-95: Enhance JSR-223 implementation
+* JEXL-94: Allow stateful namespaces (ns:function)
+* JEXL-93: Add public fields as targets of set/get property
+* JEXL-92: JexlContext API should be more flexible
+* JEXL-89: Drop main() and suite() methods from Test cases
+* JEXL-85: 2.0 grammar finishing touches & debugger update
+* JEXL-82: Change foreach syntax
+* JEXL-77: Rename last Velocity originated classes
+* JEXL-72: Remove deprecated classes and methods entirely
+* JEXL-70: Add main class to allow scripts etc to be tested
+* JEXL-63: JSR-223 support
+* JEXL-61: Usage of strong references on Method/Constructor & WeakHashMap usage
+* JEXL-60: Refactor o.a.c.jexl.util and o.a.c.jexl.util.introspection
+* JEXL-58: UnifiedJEXL
+* JEXL-57: Change pom.xml to make it Netbeans Maven2 plugin friendly
+* JEXL-55: JEXL 2.0 redux, attempting to restart the effort to release 2.0
+* JEXL-54: Light performance enhancements
+* JEXL-47: Allow single-line comments with //
+* JEXL-43: Website overview does not mention method calls and new 2.0 features
+* JEXL-41: Allow nested ${} evaluation
+* JEXL-35: Final API requirements
+* JEXL-34: Remove pre and post resolution of variables via the JexlExprResolver classes.
+* JEXL-33: Remove unnecessary throws Exception from various classes
+* JEXL-29: Support non-object-level functions/methods, as size and empty function
+* JEXL-25: Call method with varargs
+* JEXL-24: Support Long for integer literal instead of Integers
+* JEXL-21: operator overloading / hooks on operator processing
+* JEXL-16: allowing quote escaping
+* JEXL-15: Needs definable functions
+* JEXL-11: Don't make null convertible into anything
+* JEXL-10: Make possible checking for unresolved variables
+
+Other Changes:
+==============
+
+o Add @since 2.0 tags to code so we can track API additions via Javadoc
+
diff --git a/COMMONS_JEXL_2_0/STATUS.html b/COMMONS_JEXL_2_0/STATUS.html
new file mode 100644
index 0000000..637dd3f
--- /dev/null
+++ b/COMMONS_JEXL_2_0/STATUS.html
@@ -0,0 +1,115 @@
+<html>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+-->
+<head>
+<title>Status File for Apache Commons "Jexl" Package</title>
+</head>
+<body bgcolor="white">
+
+
+<div align="center">
+<h1>The Apache Commons <em>Jexl</em> Package</h1>
+$Id$<br/>
+<a href="#Introduction">[Introduction]</a>
+<a href="#Dependencies">[Dependencies]</a>
+<a href="#Release Info">[Release Info]</a>
+<a href="#Committers">[Committers]</a>
+<a href="#Action Items">[Action Items]</a>
+<br/><br/>
+</div>
+
+
+<a name="Introduction"></a>
+<h3>1. INTRODUCTION</h3>
+
+<p>
+The <em>Jexl</em> package implements a simple expression language
+engine. It borrows many ideas from the JSTL Expression Language. Liberally.
+Ok, it's an attempt at an idependent implementation of the JSTL EL with
+extensions.
+</p>
+
+<a name="Dependencies"></a>
+<h3>2. DEPENDENCIES</h3>
+
+<p>The <em>Jexl</em> package is dependent upon the following external
+components for development and use:</p>
+<ul>
+<li>
+ <a href="http://java.sun.com/j2se">Java Development Kit</a>
+ (Version 1.5 or later)
+</li>
+<li>
+ <a href="http://commons.apache.org/logging/">Apache Commons Logging</a>
+ (Version 1.0.1 or later)
+</li>
+<li>
+ <a href="http://jakarta.apache.org/velocity/">Jakarta Velocity DVSL</a>
+ (any version) - for documentation generation
+</li>
+<li>
+ <a href="http://www.dom4j.org/">dom4j</a>
+ (latest version) - for documentation generation
+</li>
+<li>
+<a href="http://www.junit.org">JUnit Testing Framework</a>
+ (Version 3.8.2 or later) - for unit tests only, not required
+ for deployment
+</li>
+</ul>
+
+<a name="Release Info"></a>
+<h3>3. RELEASE INFO</h3>
+
+<p>Current Release: 1.1</p>
+
+<p>Planned Next Release: 2.0</p>
+
+<a name="Committers"></a>
+<h3>4. COMMITTERS</h3>
+
+<p>The following individuals are the primary developers and maintainers of this
+component. Developers who plan to use <em>Jexl</em> in their own
+projects are encouraged to collaborate on the future development of this
+component to ensure that it continues to meet a variety of needs.</p>
+<ul>
+ <li><a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a></li>
+ <li><a href="mailto:jstrachan@apache.org">James Strachan</a></li>
+ <li><a href="mailto:rdonkin@apache.org">Robert Burrell Donkin</a></li>
+ <li><a href="mailto:proyal@apache.org">Peter Royal</a></li>
+ <li><a href="mailto:henrib@apache.org">Henri Biestro</a></li>
+</ul>
+
+<a name="Action Items"></a><h3>5. ACTION ITEMS</h3>
+
+<p>Want to help? Here's some "to do" items the team has identified.</p>
+
+<table border="1">
+ <tr>
+ <th width="80%">Action Item</th>
+ <th width="20%">Volunteer</th>
+ </tr>
+
+ <tr>
+ <td>Please read the TODO.txt document in CVS</td>
+ <td align="center"> </td>
+ </tr>
+
+ </table>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/doap_jexl.rdf b/COMMONS_JEXL_2_0/doap_jexl.rdf
new file mode 100644
index 0000000..b15b263
--- /dev/null
+++ b/COMMONS_JEXL_2_0/doap_jexl.rdf
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+-->
+<rdf:RDF xmlns="http://usefulinc.com/ns/doap#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:asfext="http://projects.apache.org/ns/asfext#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" xmlns:doap="http://usefulinc.com/ns/doap#" xml:lang="en">
+ <Project rdf:about="http://commons.apache.org/jexl/">
+ <name>Apache Commons JEXL</name>
+ <homepage rdf:resource="http://commons.apache.org/jexl/"/>
+ <programming-language>Java</programming-language>
+ <category rdf:resource="http://projects.apache.org/category/library"/>
+ <license rdf:resource="http://usefulinc.com/doap/licenses/asl20"/>
+ <bug-database rdf:resource="http://issues.apache.org/jira/browse/JEXL"/>
+ <download-page rdf:resource="http://jakarta.apache.org/site/downloads/downloads_commons-jexl.cgi"/>
+ <asfext:pmc rdf:resource="http://commons.apache.org/"/>
+ <shortdesc xml:lang="en">Commons JEXL Expression Language Engine</shortdesc>
+ <description xml:lang="en">Jexl is an implementation of the JSTL Expression Language with extensions.</description>
+ <repository>
+ <SVNRepository>
+ <browse rdf:resource="http://svn.apache.org/repos/asf/commons/proper/jexl/trunk"/>
+ <location rdf:resource="http://svn.apache.org/repos/asf/commons/proper/jexl"/>
+ </SVNRepository>
+ </repository>
+ <release>
+ <version>
+ <name>commons-jexl</name>
+ <created>2009-11-16</created>
+ <revision>2.0-RC1</revision>
+ </version>
+ <version>
+ <name>commons-jexl</name>
+ <created>2009-03-25</created>
+ <revision>1.1</revision>
+ </version>
+ </release>
+ <mailing-list rdf:resource="http://commons.apache.org/mail-lists.html"/>
+ </Project>
+</rdf:RDF>
diff --git a/COMMONS_JEXL_2_0/jexl2-compat/pom.xml b/COMMONS_JEXL_2_0/jexl2-compat/pom.xml
new file mode 100644
index 0000000..b217c2a
--- /dev/null
+++ b/COMMONS_JEXL_2_0/jexl2-compat/pom.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-parent</artifactId>
+ <version>12</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-jexl-compat</artifactId>
+ <name>Commons JEXL (1.x compatibility)</name>
+ <version>2.0-SNAPSHOT</version>
+ <inceptionYear>2003</inceptionYear>
+ <description>Jexl is an implementation of the JSTL Expression Language with extensions.</description>
+ <url>http://commons.apache.org/jexl/</url>
+
+ <issueManagement>
+ <system>jira</system>
+ <url>http://issues.apache.org/jira/browse/JEXL</url>
+ </issueManagement>
+
+ <scm>
+ <connection>scm:svn:http://svn.apache.org/repos/asf/commons/proper/jexl/trunk</connection>
+ <developerConnection>scm:svn:https://svn.apache.org/repos/asf/commons/proper/jexl/trunk</developerConnection>
+ <url>http://svn.apache.org/viewvc/commons/proper/jexl/trunk</url>
+ </scm>
+
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <includes>
+ <include>**/*Test.java</include>
+ </includes>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.5</source>
+ <target>1.5</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>3.8.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ <version>1.1.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-jexl</artifactId>
+ <version>2.0-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+
+ <properties>
+ <maven.compile.source>1.5</maven.compile.source>
+ <maven.compile.target>1.5</maven.compile.target>
+ <commons.componentid>jexl-compat</commons.componentid>
+ <commons.release.version>2.0</commons.release.version>
+ <!-- The RC version used in the staging repository URL. -->
+ <commons.rc.version>RC3</commons.rc.version>
+ <commons.binary.suffix />
+ <commons.jira.id>JEXL</commons.jira.id>
+ <commons.jira.pid>12310479</commons.jira.pid>
+ <!-- Temp fix until parent POM is updated -->
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+ </properties>
+</project>
+
diff --git a/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/Expression.java b/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/Expression.java
new file mode 100644
index 0000000..5aa20df
--- /dev/null
+++ b/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/Expression.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl;
+
+/**
+ * Jexl-1.x compatible expression.
+ * @since 2.0
+ * @version $Id$
+ */
+public interface Expression extends org.apache.commons.jexl2.Expression {
+ /**
+ * Evaluates the expression with the variables contained in the
+ * supplied {@link JexlContext}.
+ *
+ * @param context A JexlContext containing variables.
+ * @return The result of this evaluation
+ * @throws Exception on any error
+ */
+ Object evaluate(JexlContext context) throws Exception;
+}
diff --git a/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/ExpressionFactory.java b/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/ExpressionFactory.java
new file mode 100644
index 0000000..83b9db5
--- /dev/null
+++ b/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/ExpressionFactory.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl;
+
+import org.apache.commons.jexl2.JexlEngine;
+
+/**
+ * Creates Expression objects.
+ * <p>
+ * To create a JEXL Expression object, pass
+ * valid JEXL syntax to the static createExpression() method:
+ * </p>
+ *
+ * <pre>
+ * String jexl = "array[1]";
+ * Expression expression = ExpressionFactory.createExpression( jexl );
+ * </pre>
+ *
+ * <p>
+ * When an {@link Expression} object is created, the JEXL syntax is
+ * parsed and verified. If the supplied expression is neither an
+ * expression nor a reference, an exception is thrown from createException().
+ * </p>
+ *
+ * <p>
+ * This is a convenience class; using an instance of a {@link JexlEngine}
+ * that serves the same purpose with more control is recommended.
+ * </p>
+ * @since 1.0
+ * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a>
+ * @version $Id: ExpressionFactory.java 884175 2009-11-25 16:23:41Z henrib $
+ * @deprecated Create a JexlEngine and use the createScript method on that instead.
+ */
+@Deprecated
+public final class ExpressionFactory extends JexlOne {}
diff --git a/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/JexlContext.java b/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/JexlContext.java
new file mode 100644
index 0000000..dc22c41
--- /dev/null
+++ b/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/JexlContext.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl;
+
+import java.util.Map;
+
+/**
+ * Holds a Map of variables which are referenced in a JEXL expression.
+ *
+ * @since 1.0
+ * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a>
+ * @version $Id: JexlContext.java 480412 2006-11-29 05:11:23Z bayard $
+ */
+public interface JexlContext {
+ /**
+ * Replaces variables in a JexlContext with the variables contained
+ * in the supplied Map. When setVars() is called on a JexlContext,
+ * it clears the current Map and puts each entry of the
+ * supplied Map into the current variable Map.
+ *
+ * @param vars Contents of vars will be replaced with the content
+ * of this Map
+ */
+ void setVars(Map<String,Object> vars);
+
+ /**
+ * Retrives the Map of variables associated with this JexlContext. The
+ * keys of this map correspond to variable names referenced in a
+ * JEXL expression.
+ *
+ * @return A reference to the variable Map associated with this JexlContext.
+ */
+ Map<String,Object> getVars();
+}
diff --git a/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/JexlHelper.java b/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/JexlHelper.java
new file mode 100644
index 0000000..c9d87ea
--- /dev/null
+++ b/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/JexlHelper.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl;
+
+import org.apache.commons.jexl.context.HashMapContext;
+
+/**
+ * Helper to create a context. In the current implementation of JEXL, there
+ * is one implementation of JexlContext - {@link HashMapContext}, and there
+ * is no reason not to directly instantiate {@link HashMapContext} in your
+ * own application.
+ *
+ * @since 1.0
+ * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a>
+ * @version $Id: JexlHelper.java 480412 2006-11-29 05:11:23Z bayard $
+ */
+public class JexlHelper {
+ /** singleton instance. */
+ protected static JexlHelper helper = new JexlHelper();
+
+ /** @return the single instance. */
+ protected static JexlHelper getInstance() {
+ return helper;
+ }
+
+ /**
+ * Returns a new {@link JexlContext}.
+ * @return a new JexlContext
+ */
+ public static JexlContext createContext() {
+ return getInstance().newContext();
+ }
+
+ /**
+ * Creates and returns a new {@link JexlContext}.
+ * The current implementation creates a new instance of
+ * {@link HashMapContext}.
+ * @return a new JexlContext
+ */
+ protected JexlContext newContext() {
+ return new HashMapContext();
+ }
+}
diff --git a/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/JexlOne.java b/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/JexlOne.java
new file mode 100644
index 0000000..70bd01c
--- /dev/null
+++ b/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/JexlOne.java
@@ -0,0 +1,277 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl;
+
+import java.io.File;
+import java.net.URL;
+import org.apache.commons.jexl2.JexlEngine;
+import org.apache.commons.jexl2.Interpreter;
+import org.apache.commons.jexl2.JexlException;
+import org.apache.commons.jexl2.parser.JexlNode;
+import org.apache.commons.jexl2.parser.ASTJexlScript;
+
+/**
+ * This implements Jexl-1.x (Jelly) compatible behaviors on top of Jexl-2.0.
+ * @since 2.0
+ * @version $Id$
+ */
+public class JexlOne {
+ /**
+ * Default cache size.
+ */
+ private static final int CACHE_SIZE = 256;
+
+ /**
+ * Private constructor, ensure no instance.
+ */
+ protected JexlOne() {}
+
+
+ /**
+ * Lazy JexlEngine singleton through on demand holder idiom.
+ */
+ private static final class EngineHolder {
+ /** The shared instance. */
+ static final JexlOneEngine JEXL10 = new JexlOneEngine();
+ /**
+ * Non-instantiable.
+ */
+ private EngineHolder() {}
+ }
+
+
+ /**
+ * A Jexl1.x context wrapped into a Jexl2 context.
+ */
+ private static final class ContextAdapter implements org.apache.commons.jexl2.JexlContext {
+ /** The Jexl1.x context. */
+ private final JexlContext legacy;
+
+ /**
+ * Creates a jexl2.JexlContext from a jexl.JexlContext.
+ * @param ctxt10
+ */
+ ContextAdapter(JexlContext ctxt10) {
+ legacy = ctxt10;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object get(String name) {
+ return legacy.getVars().get(name);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void set(String name, Object value) {
+ legacy.getVars().put(name, value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean has(String name) {
+ return legacy.getVars().containsKey(name);
+ }
+
+ /**
+ * Adapts a Jexl-1.x context to a Jexl-2.0 context.
+ * @param aContext a oac.jexl context
+ * @return an oac.jexl2 context
+ */
+ static final org.apache.commons.jexl2.JexlContext adapt(JexlContext aContext) {
+ return aContext == null ? JexlOneEngine.EMPTY_CONTEXT : new ContextAdapter(aContext);
+ }
+ }
+
+
+ /**
+ * An interpreter made compatible with v1.1 behavior (at least Jelly's expectations).
+ */
+ private static final class JexlOneInterpreter extends Interpreter {
+ /**
+ * Creates an instance.
+ * @param jexl the jexl engine
+ * @param aContext the jexl context
+ */
+ public JexlOneInterpreter(JexlEngine jexl, JexlContext aContext) {
+ super(jexl, ContextAdapter.adapt(aContext));
+ }
+
+ /**{@inheritDoc}*/
+ @Override
+ public Object interpret(JexlNode node) {
+ try {
+ return node.jjtAccept(this, null);
+ } catch (JexlException xjexl) {
+ Throwable e = xjexl.getCause();
+ if (e instanceof RuntimeException) {
+ throw (RuntimeException) e;
+ }
+ if (e instanceof IllegalStateException) {
+ throw (IllegalStateException) e;
+ }
+ throw new IllegalStateException(e.getMessage(), e);
+ }
+ }
+
+ /**{@inheritDoc}*/
+ @Override
+ protected Object invocationFailed(JexlException xjexl) {
+ throw xjexl;
+ }
+
+ /**{@inheritDoc}*/
+ @Override
+ protected Object unknownVariable(JexlException xjexl) {
+ return null;
+ }
+ }
+
+
+ /**
+ * An engine that uses a JexlOneInterpreter.
+ */
+ private static final class JexlOneEngine extends JexlEngine {
+ /**
+ * Default ctor, creates a cache and sets instance to verbose (ie non-silent).
+ */
+ private JexlOneEngine() {
+ super();
+ setCache(CACHE_SIZE);
+ setSilent(false);
+ }
+
+ /**{@inheritDoc}*/
+ protected Interpreter createInterpreter(JexlContext context) {
+ return new JexlOneInterpreter(this, context);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected Script createScript(ASTJexlScript tree, String text) {
+ return new JexlOneExpression(this, text, tree);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected Expression createExpression(ASTJexlScript tree, String text) {
+ return new JexlOneExpression(this, text, tree);
+ }
+ }
+
+ /**
+ * The specific Jexl-1.x expressions implementation.
+ */
+ private static final class JexlOneExpression
+ extends org.apache.commons.jexl2.ExpressionImpl
+ implements Expression, Script {
+ /**
+ * Default local ctor.
+ *
+ * @param engine the interpreter to evaluate the expression
+ * @param expr the expression.
+ * @param ref the parsed expression.
+ */
+ private JexlOneExpression(JexlOne.JexlOneEngine engine, String expr, ASTJexlScript ref) {
+ super(engine, expr, ref);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object evaluate(JexlContext context) {
+ return super.evaluate(ContextAdapter.adapt(context));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object execute(JexlContext context) {
+ return super.execute(ContextAdapter.adapt(context));
+ }
+ }
+
+
+ /**
+ * Creates a Script from a String containing valid JEXL syntax.
+ * This method parses the script which validates the syntax.
+ *
+ * @param scriptText A String containing valid JEXL syntax
+ * @return A {@link Script} which can be executed with a
+ * {@link JexlContext}.
+ * @throws Exception An exception can be thrown if there is a
+ * problem parsing the script.
+ * @deprecated Create a JexlEngine and use the createScript method on that instead.
+ */
+ @Deprecated
+ public static Script createScript(String scriptText) throws Exception {
+ return (Script) EngineHolder.JEXL10.createScript(scriptText);
+ }
+
+ /**
+ * Creates a Script from a {@link File} containing valid JEXL syntax.
+ * This method parses the script and validates the syntax.
+ *
+ * @param scriptFile A {@link File} containing valid JEXL syntax.
+ * Must not be null. Must be a readable file.
+ * @return A {@link Script} which can be executed with a
+ * {@link JexlContext}.
+ * @throws Exception An exception can be thrown if there is a problem
+ * parsing the script.
+ * @deprecated Create a JexlEngine and use the createScript method on that instead.
+ */
+ @Deprecated
+ public static Script createScript(File scriptFile) throws Exception {
+ return (Script) EngineHolder.JEXL10.createScript(scriptFile);
+ }
+
+ /**
+ * Creates a Script from a {@link URL} containing valid JEXL syntax.
+ * This method parses the script and validates the syntax.
+ *
+ * @param scriptUrl A {@link URL} containing valid JEXL syntax.
+ * Must not be null. Must be a readable file.
+ * @return A {@link Script} which can be executed with a
+ * {@link JexlContext}.
+ * @throws Exception An exception can be thrown if there is a problem
+ * parsing the script.
+ * @deprecated Create a JexlEngine and use the createScript method on that instead.
+ */
+ @Deprecated
+ public static Script createScript(URL scriptUrl) throws Exception {
+ return (Script) EngineHolder.JEXL10.createScript(scriptUrl);
+ }
+
+ /**
+ * Creates an Expression from a String containing valid
+ * JEXL syntax. This method parses the expression which
+ * must contain either a reference or an expression.
+ * @param expression A String containing valid JEXL syntax
+ * @return An Expression object which can be evaluated with a JexlContext
+ * @throws JexlException An exception can be thrown if there is a problem
+ * parsing this expression, or if the expression is neither an
+ * expression or a reference.
+ * @deprecated Create a JexlEngine and use createExpression() on that
+ */
+ @Deprecated
+ public static Expression createExpression(String expression) {
+ return (Expression) EngineHolder.JEXL10.createExpression(expression);
+ }
+}
diff --git a/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/Script.java b/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/Script.java
new file mode 100644
index 0000000..93f3089
--- /dev/null
+++ b/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/Script.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl;
+
+/**
+ * Jexl-1.x compatible script.
+ * @since 2.0
+ * @version $Id$
+ */
+public interface Script extends org.apache.commons.jexl2.Script {
+ /**
+ * Executes the script with the variables contained in the
+ * supplied {@link JexlContext}.
+ *
+ * @param context A JexlContext containing variables.
+ * @return The result of this script, usually the result of
+ * the last statement.
+ * @throws Exception on any script parse or execution error.
+ */
+ Object execute(JexlContext context) throws Exception;
+}
diff --git a/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/ScriptFactory.java b/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/ScriptFactory.java
new file mode 100644
index 0000000..6de844f
--- /dev/null
+++ b/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/ScriptFactory.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl;
+
+import org.apache.commons.jexl2.JexlEngine;
+
+/**
+ * <p>
+ * Creates {@link Script}s. To create a JEXL Script, pass
+ * valid JEXL syntax to the static createScript() method:
+ * </p>
+ *
+ * <pre>
+ * String jexl = "y = x * 12 + 44; y = y * 4;";
+ * Script script = ScriptFactory.createScript( jexl );
+ * </pre>
+ *
+ * <p>
+ * When an {@link Script} is created, the JEXL syntax is
+ * parsed and verified.
+ * </p>
+ *
+ * <p>
+ * This is a convenience class; using an instance of a {@link JexlEngine}
+ * that serves the same purpose with more control is recommended.
+ * </p>
+ * @since 1.1
+ * @version $Id: ScriptFactory.java 884175 2009-11-25 16:23:41Z henrib $
+ * @deprecated Create a JexlEngine and use the createScript method on that instead.
+ */
+@Deprecated
+public final class ScriptFactory extends JexlOne {}
+
diff --git a/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/context/HashMapContext.java b/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/context/HashMapContext.java
new file mode 100644
index 0000000..4dd2bcf
--- /dev/null
+++ b/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/context/HashMapContext.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl.context;
+
+import org.apache.commons.jexl.JexlContext;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Implementation of JexlContext based on a HashMap.
+ *
+ * @since 1.0
+ * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a>
+ * @version $Id: HashMapContext.java 706202 2008-10-20 10:28:25Z sebb $
+ */
+public class HashMapContext extends HashMap<String,Object> implements JexlContext {
+ /** serialization version id jdk13 generated. */
+ private static final long serialVersionUID = 5715964743204418854L;
+ /**
+ * {@inheritDoc}
+ */
+ public void setVars(Map<String,Object> vars) {
+ clear();
+ putAll(vars);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Map<String,Object> getVars() {
+ return this;
+ }
+}
diff --git a/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/context/package.html b/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/context/package.html
new file mode 100644
index 0000000..70f6e48
--- /dev/null
+++ b/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/context/package.html
@@ -0,0 +1,36 @@
+<html>
+ <!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+ -->
+ <head>
+ <title>Package Documentation for org.apache.commons.jexl.context Package</title>
+ </head>
+ <body bgcolor="white">
+ Simple JexlContext implementations.
+ <br/><br/>
+ <p>
+ <ul>
+ <li><a href="#intro">Introduction</a></li>
+ </ul>
+ </p>
+ <h2><a name="intro">Introduction</a></h2>
+ <p>
+ This package only contains one JexlContext implementation, the
+ HashMapContext. A HashMapContext is simply an extension of
+ HashMap which implements the JexlContext interface.
+ </p>
+</body>
+</html>
diff --git a/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/package.html b/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/package.html
new file mode 100644
index 0000000..26dd052
--- /dev/null
+++ b/COMMONS_JEXL_2_0/jexl2-compat/src/main/java/org/apache/commons/jexl/package.html
@@ -0,0 +1,38 @@
+<html>
+ <!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+ -->
+ <head>
+ <title>Package Documentation for org.apache.commons.jexl Package</title>
+ </head>
+ <body bgcolor="white">
+ <h2>Jexl-1.x compatible implementation.</h2>
+ <p>
+ This package only contains classes that re-implement Jexl-1.x interfaces
+ and behaviors. This is intended to allow easier conversion to Jexl-2.0.
+ </p>
+ <p>
+ Jexl-2.0 changed a lot of APIs and behaviors, enough to warrant putting it
+ in its own org.apache.commons.jexl2 package and avoid possible "jar-hell" cases.
+ Those could have occured if someone was trying to use both Jexl-2.0 and Jexl-1.x.
+ </p>
+ <p>
+ This package contains the original Jexl-1.x main API namely ScriptFactory, ExpressionFactory,
+ Script, Expression, JexlContext and JexlHelper. It is not 100% compatible with
+ the original Jexl-1.x codeline but should be close enough for "casual" usage.
+ </p>
+ </body>
+</html>
diff --git a/COMMONS_JEXL_2_0/jexl2-compat/src/test/java/org/apache/commons/jexl/ScriptTest.java b/COMMONS_JEXL_2_0/jexl2-compat/src/test/java/org/apache/commons/jexl/ScriptTest.java
new file mode 100644
index 0000000..5765942
--- /dev/null
+++ b/COMMONS_JEXL_2_0/jexl2-compat/src/test/java/org/apache/commons/jexl/ScriptTest.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl;
+import junit.framework.TestCase;
+import java.io.File;
+import java.net.URL;
+
+/**
+ * Tests for Script
+ * @since 1.1
+ */
+public class ScriptTest extends TestCase {
+ static final String TEST1 = "../src/test/scripts/test1.jexl";
+
+ // test class for testScriptUpdatesContext
+ // making this class private static will cause the test to fail.
+ // this is due to unusual code in ClassMap.getAccessibleMethods(Class)
+ // that treats non-public classes in a specific way. Why getAccessibleMethods
+ // does this is not known yet.
+ public static class Tester {
+ private String code;
+ public String getCode () {
+ return code;
+ }
+ public void setCode(String c) {
+ code = c;
+ }
+ }
+ /**
+ * Create a new test case.
+ * @param name case name
+ */
+ public ScriptTest(String name) {
+ super(name);
+ }
+
+ /**
+ * Test creating a script from a string.
+ */
+ public void testSimpleScript() throws Exception {
+ String code = "while (x < 10) x = x + 1;";
+ Script s = ScriptFactory.createScript(code);
+ JexlContext jc = JexlHelper.createContext();
+ jc.getVars().put("x", new Integer(1));
+
+ Object o = s.execute(jc);
+ assertEquals("Result is wrong", new Integer(10), o);
+ assertEquals("getText is wrong", code, s.getText());
+ }
+
+ public void testScriptFromFile() throws Exception {
+ File testScript = new File(TEST1);
+ Script s = ScriptFactory.createScript(testScript);
+ JexlContext jc = JexlHelper.createContext();
+ jc.getVars().put("out", System.out);
+ Object result = s.execute(jc);
+ assertNotNull("No result", result);
+ assertEquals("Wrong result", new Integer(7), result);
+ }
+
+ public void testScriptFromURL() throws Exception {
+ URL testUrl = new File(TEST1).toURI().toURL();
+ Script s = ScriptFactory.createScript(testUrl);
+ JexlContext jc = JexlHelper.createContext();
+ jc.getVars().put("out", System.out);
+ Object result = s.execute(jc);
+ assertNotNull("No result", result);
+ assertEquals("Wrong result", new Integer(7), result);
+ }
+
+ public void testScriptUpdatesContext() throws Exception {
+ String jexlCode = "resultat.setCode('OK')";
+ Expression e = ExpressionFactory.createExpression(jexlCode);
+ Script s = ScriptFactory.createScript(jexlCode);
+
+ Tester resultatJexl = new Tester();
+ JexlContext jc = JexlHelper.createContext();
+ jc.getVars().put("resultat", resultatJexl);
+
+ resultatJexl.setCode("");
+ e.evaluate(jc);
+ assertEquals("OK", resultatJexl.getCode());
+ resultatJexl.setCode("");
+ s.execute(jc);
+ assertEquals("OK", resultatJexl.getCode());
+ }
+
+}
diff --git a/COMMONS_JEXL_2_0/pom.xml b/COMMONS_JEXL_2_0/pom.xml
new file mode 100644
index 0000000..15f44e1
--- /dev/null
+++ b/COMMONS_JEXL_2_0/pom.xml
@@ -0,0 +1,330 @@
+<?xml version="1.0"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-parent</artifactId>
+ <version>12</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-jexl</artifactId>
+ <version>2.0</version>
+ <name>Commons JEXL</name>
+ <inceptionYear>2001</inceptionYear>
+ <description>Jexl is an implementation of the JSTL Expression Language with extensions.</description>
+ <url>http://commons.apache.org/jexl/</url>
+
+ <issueManagement>
+ <system>jira</system>
+ <url>http://issues.apache.org/jira/browse/JEXL</url>
+ </issueManagement>
+
+ <scm>
+ <connection>scm:svn:http://svn.apache.org/repos/asf/commons/proper/jexl/tags/COMMONS_JEXL_2_0-RC6</connection>
+ <developerConnection>scm:svn:https://svn.apache.org/repos/asf/commons/proper/jexl/tags/COMMONS_JEXL_2_0-RC6</developerConnection>
+ <url>http://svn.apache.org/viewvc/commons/proper/jexl/tags/COMMONS_JEXL_2_0-RC6</url>
+ </scm>
+
+ <developers>
+ <developer>
+ <name>dIon Gillard</name>
+ <id>dion</id>
+ <email>dion AT apache DOT org</email>
+ <organization>Apache Software Foundation</organization>
+ </developer>
+ <developer>
+ <name>Geir Magnusson Jr.</name>
+ <id>geirm</id>
+ <email>geirm AT apache DOT org</email>
+ <organization>independent</organization>
+ </developer>
+ <developer>
+ <name>Tim O'Brien</name>
+ <id>tobrien</id>
+ <email>tobrien AT apache DOT org</email>
+ <organization>independent</organization>
+ </developer>
+ <developer>
+ <name>Peter Royal</name>
+ <id>proyal</id>
+ <email>proyal AT apache DOT org</email>
+ <organization>Apache Software Foundation</organization>
+ </developer>
+ <developer>
+ <name>James Strachan</name>
+ <id>jstrachan</id>
+ <email>jstrachan AT apache DOT org</email>
+ <organization>SpiritSoft, Inc.</organization>
+ </developer>
+ <developer>
+ <name>Rahul Akolkar</name>
+ <id>rahul</id>
+ <email>rahul AT apache DOT org</email>
+ <organization>Apache Software Foundation</organization>
+ </developer>
+ <developer>
+ <name>Sebastian Bazley</name>
+ <id>sebb</id>
+ <email>sebb AT apache DOT org</email>
+ </developer>
+ <developer>
+ <name>Henri Biestro</name>
+ <id>henrib</id>
+ <email>henrib AT apache DOT org</email>
+ </developer>
+ </developers>
+
+ <dependencies>
+ <dependency>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ <version>1.1.1</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>3.8.2</version>
+ <scope>test</scope>
+ </dependency>
+ <!-- For JSR-223 API -->
+ <dependency>
+ <groupId>org.apache.bsf</groupId>
+ <artifactId>bsf-api</artifactId>
+ <version>[3.0-beta3,)</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <properties>
+ <maven.compile.source>1.5</maven.compile.source>
+ <maven.compile.target>1.5</maven.compile.target>
+ <commons.componentid>jexl</commons.componentid>
+ <commons.release.version>2.0</commons.release.version>
+ <!-- The RC version used in the staging repository URL. -->
+ <commons.rc.version>RC6</commons.rc.version>
+ <commons.binary.suffix />
+ <commons.jira.id>JEXL</commons.jira.id>
+ <commons.jira.pid>12310479</commons.jira.pid>
+ <!-- Temp fix until parent POM is updated -->
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+ </properties>
+
+ <build>
+ <!-- temporarily override the parent POM (v 11) until that is updated -->
+ <resources>
+ <resource>
+ <directory>${basedir}</directory>
+ <targetPath>META-INF</targetPath>
+ <includes>
+ <include>NOTICE.txt</include>
+ <include>LICENSE.txt</include>
+ </includes>
+ </resource>
+ <!-- This is the default, but is currently missing from the parent POM (v11) -->
+ <resource>
+ <directory>src/main/resources</directory>
+ </resource>
+ </resources>
+ <plugins>
+ <!-- workaround MRELEASE-424 when publishing -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-release-plugin</artifactId>
+ <configuration>
+ <mavenExecutorId>forked-path</mavenExecutorId>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <includes>
+ <include>**/*Test.java</include>
+ </includes>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <configuration>
+ <descriptors>
+ <descriptor>src/main/assembly/bin.xml</descriptor>
+ <descriptor>src/main/assembly/src.xml</descriptor>
+ </descriptors>
+ <tarLongFileMode>gnu</tarLongFileMode>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>javacc-maven-plugin</artifactId>
+ <version>2.6</version>
+ <executions>
+ <execution>
+ <id>jexl-jjtree</id>
+ <configuration>
+ <sourceDirectory>${basedir}/src/main/java/org/apache/commons/jexl2/parser</sourceDirectory>
+ <outputDirectory>${project.build.directory}/generated-sources/java</outputDirectory>
+ <timestampDirectory>${project.build.directory}/generated-sources/javacc-timestamp</timestampDirectory>
+ <packageName>org.apache.commons.jexl2.parser</packageName>
+ </configuration>
+ <goals>
+ <goal>jjtree-javacc</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <!--
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>clirr-maven-plugin</artifactId>
+ <version>2.2.2</version>
+ <configuration>
+ <comparisonArtifacts>
+ <comparisonArtifact>
+ <groupId>commons-jexl</groupId>
+ <artifactId>commons-jexl</artifactId>
+ <version>1.1</version>
+ </comparisonArtifact>
+ </comparisonArtifacts>
+ </configuration>
+ </plugin>
+ -->
+ </plugins>
+ </build>
+
+ <reporting>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-changes-plugin</artifactId>
+ <version>2.1</version>
+ <configuration>
+ <xmlPath>${basedir}/xdocs/changes.xml</xmlPath>
+ <issueLinkTemplate>%URL%/%ISSUE%</issueLinkTemplate>
+ </configuration>
+ <reportSets>
+ <reportSet>
+ <reports>
+ <report>changes-report</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ <version>2.3</version>
+ <configuration>
+ <configLocation>${basedir}/src/main/config/checkstyle.xml</configLocation>
+ <excludes>org/apache/commons/jexl2/parser/*.java</excludes>
+ <headerFile>${basedir}/src/main/config/header.txt</headerFile>
+ <enableRulesSummary>false</enableRulesSummary>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>cobertura-maven-plugin</artifactId>
+ <version>2.3</version>
+ <configuration>
+ <instrumentation>
+ <excludes>
+ <exclude>org/apache/commons/jexl2/parser/*.class</exclude>
+ <exclude>org/apache/commons/jexl2/**/*Test.class</exclude>
+ </excludes>
+ </instrumentation>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>findbugs-maven-plugin</artifactId>
+ <version>2.1</version>
+ <configuration>
+ <excludes>
+ <exclude>org/apache/commons/jexl2/parser/*.class</exclude>
+ <exclude>org/apache/commons/jexl2/**/*Test.class</exclude>
+ </excludes>
+ <xmlOutput>true</xmlOutput>
+ <!-- Optional directory to put findbugs xdoc xml report -->
+ <xmlOutputDirectory>target/site</xmlOutputDirectory>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-pmd-plugin</artifactId>
+ <version>2.4</version>
+ <configuration>
+ <targetJdk>1.5</targetJdk>
+ <excludes>
+ <exclude>org/apache/commons/jexl2/parser/*.class</exclude>
+ <exclude>org/apache/commons/jexl2/**/*Test.class</exclude>
+ </excludes>
+ <rulesets>
+ <ruleset>/rulesets/braces.xml</ruleset>
+ <ruleset>/rulesets/unusedcode.xml</ruleset>
+ <ruleset>/rulesets/imports.xml</ruleset>
+ <ruleset>/rulesets/codesize.xml</ruleset>
+ <ruleset>/rulesets/coupling.xml</ruleset>
+ <ruleset>/rulesets/design.xml</ruleset>
+ <ruleset>/rulesets/strings.xml</ruleset>
+ </rulesets>
+ </configuration>
+ </plugin>
+ <!--
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>clirr-maven-plugin</artifactId>
+ <version>2.2.2</version>
+ <configuration>
+ <comparisonArtifacts>
+ <comparisonArtifact>
+ <groupId>commons-jexl</groupId>
+ <artifactId>commons-jexl</artifactId>
+ <version>1.1</version>
+ </comparisonArtifact>
+ </comparisonArtifacts>
+ </configuration>
+ </plugin>
+ -->
+ </plugins>
+ </reporting>
+
+
+ <distributionManagement>
+ <site>
+ <id>website</id>
+ <name>Apache Website</name>
+ <url>${commons.deployment.protocol}://people.apache.org/www/commons.apache.org/jexl/</url>
+ </site>
+ </distributionManagement>
+
+ <profiles>
+ <profile>
+ <id>rc</id>
+ <distributionManagement>
+ <!-- Cannot define in parent ATM, see COMMONSSITE-26 -->
+ <site>
+ <id>apache.website</id>
+ <name>Apache Commons Release Candidate Staging Site</name>
+ <url>${commons.deployment.protocol}://people.apache.org/www/people.apache.org/builds/commons/${commons.componentid}/${commons.release.version}/${commons.rc.version}/site</url>
+ </site>
+ </distributionManagement>
+ </profile>
+ </profiles>
+
+</project>
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/main/assembly/bin.xml b/COMMONS_JEXL_2_0/src/main/assembly/bin.xml
new file mode 100644
index 0000000..5762291
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/assembly/bin.xml
@@ -0,0 +1,44 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+-->
+<assembly>
+ <id>bin</id>
+ <formats>
+ <format>tar.gz</format>
+ <format>zip</format>
+ </formats>
+ <includeSiteDirectory>false</includeSiteDirectory>
+ <fileSets>
+ <fileSet>
+ <includes>
+ <include>LICENSE.txt</include>
+ <include>NOTICE.txt</include>
+ <include>RELEASE-NOTES.txt</include>
+ </includes>
+ </fileSet>
+ <fileSet>
+ <directory>target</directory>
+ <outputDirectory></outputDirectory>
+ <includes>
+ <include>*.jar</include>
+ </includes>
+ </fileSet>
+ <fileSet>
+ <directory>target/site/apidocs</directory>
+ <outputDirectory>apidocs</outputDirectory>
+ </fileSet>
+ </fileSets>
+</assembly>
diff --git a/COMMONS_JEXL_2_0/src/main/assembly/src.xml b/COMMONS_JEXL_2_0/src/main/assembly/src.xml
new file mode 100644
index 0000000..f2b63ea
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/assembly/src.xml
@@ -0,0 +1,40 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+-->
+<assembly>
+ <id>src</id>
+ <formats>
+ <format>tar.gz</format>
+ <format>zip</format>
+ </formats>
+ <baseDirectory>${artifactId}-${commons.release.version}-src</baseDirectory>
+ <fileSets>
+ <fileSet>
+ <includes>
+ <include>LICENSE.txt</include>
+ <include>NOTICE.txt</include>
+ <include>pom.xml</include>
+ <include>RELEASE-NOTES.txt</include>
+ </includes>
+ </fileSet>
+ <fileSet>
+ <directory>src</directory>
+ </fileSet>
+ <fileSet>
+ <directory>xdocs</directory>
+ </fileSet>
+ </fileSets>
+</assembly>
diff --git a/COMMONS_JEXL_2_0/src/main/config/checkstyle.xml b/COMMONS_JEXL_2_0/src/main/config/checkstyle.xml
new file mode 100644
index 0000000..159e0a9
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/config/checkstyle.xml
@@ -0,0 +1,220 @@
+<?xml version="1.0"?>
+<!--
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+ -->
+
+<!DOCTYPE module PUBLIC
+ "-//Puppy Crawl//DTD Check Configuration 1.1//EN"
+ "http://www.puppycrawl.com/dtds/configuration_1_1.dtd">
+
+<!--
+
+ Checkstyle configuration that checks the sun coding conventions from:
+
+ - the Java Language Specification at
+ http://java.sun.com/docs/books/jls/second_edition/html/index.html
+
+ - the Sun Code Conventions at http://java.sun.com/docs/codeconv/
+
+ - the Javadoc guidelines at
+ http://java.sun.com/j2se/javadoc/writingdoccomments/index.html
+
+ - the JDK Api documentation http://java.sun.com/j2se/docs/api/index.html
+
+ - some best practices
+
+ Checkstyle is very configurable. Be sure to read the documentation at
+ http://checkstyle.sf.net (or in your downloaded distribution).
+
+ Most Checks are configurable, be sure to consult the documentation.
+
+ To completely disable a check, just comment it out or delete it from the file.
+
+ Finally, it is worth reading the documentation.
+
+-->
+
+<module name="Checker">
+
+ <!-- Checks that a package.html file exists for each package. -->
+ <!-- See http://checkstyle.sf.net/config_javadoc.html#PackageHtml -->
+ <module name="PackageHtml"/>
+
+ <!-- Checks whether files end with a new line. -->
+ <!-- See http://checkstyle.sf.net/config_misc.html#NewlineAtEndOfFile -->
+ <!-- JEXL-20: tried every combination on a Mac to no avail...
+ <module name="NewlineAtEndOfFile">
+ <property name="lineSeparator" value="lf"/>
+ </module>
+ -->
+
+ <!-- Checks that property files contain the same keys. -->
+ <!-- See http://checkstyle.sf.net/config_misc.html#Translation -->
+ <module name="Translation"/>
+
+
+ <module name="TreeWalker">
+
+ <property name="cacheFile" value="${checkstyle.cache.file}"/>
+
+ <!-- Checks for Javadoc comments. -->
+ <!-- See http://checkstyle.sf.net/config_javadoc.html -->
+ <!-- JEXL-20: many of those by API choice -->
+ <module name="JavadocMethod">
+ <property name="allowUndeclaredRTE" value="true"/>
+ </module>
+ <module name="JavadocType"/>
+ <module name="JavadocVariable"/>
+ <module name="JavadocStyle"/>
+
+
+ <!-- Checks for Naming Conventions. -->
+ <!-- See http://checkstyle.sf.net/config_naming.html -->
+ <module name="ConstantName"/>
+ <module name="LocalFinalVariableName"/>
+ <module name="LocalVariableName"/>
+ <module name="MemberName"/>
+ <module name="MethodName"/>
+ <module name="PackageName"/>
+ <module name="ParameterName"/>
+ <module name="StaticVariableName"/>
+ <module name="TypeName"/>
+
+
+ <!-- Checks for Headers -->
+ <!-- See http://checkstyle.sf.net/config_header.html -->
+ <module name="Header">
+ <!-- The follow property value demonstrates the ability -->
+ <!-- to have access to ANT properties. In this case it uses -->
+ <!-- the ${basedir} property to allow Checkstyle to be run -->
+ <!-- from any directory within a project. See property -->
+ <!-- expansion, -->
+ <!-- http://checkstyle.sf.net/config.html#properties -->
+ <!-- <property -->
+ <!-- name="headerFile" -->
+ <!-- value="${basedir}/java.header"/> -->
+ <property name="headerFile" value="${checkstyle.header.file}"/>
+ <property name="ignoreLines" value="2"/>
+ </module>
+
+ <!-- Following interprets the header file as regular expressions. -->
+ <!-- <module name="RegexpHeader"/> -->
+
+
+ <!-- Checks for imports -->
+ <!-- See http://checkstyle.sf.net/config_import.html -->
+ <module name="AvoidStarImport"/>
+ <module name="IllegalImport"/> <!-- defaults to sun.* packages -->
+ <module name="RedundantImport"/>
+ <module name="UnusedImports"/>
+
+
+ <!-- Checks for Size Violations. -->
+ <!-- See http://checkstyle.sf.net/config_sizes.html -->
+ <module name="FileLength"/>
+ <module name="LineLength">
+ <!-- JEXL: added max, ignore @version and @see -->
+ <property name="max" value="120"/>
+ <property name="ignorePattern" value="@version"/>
+ <property name="ignorePattern" value="@see"/>
+ </module>
+ <module name="MethodLength"/>
+ <module name="ParameterNumber"/>
+
+
+ <!-- Checks for whitespace -->
+ <!-- See http://checkstyle.sf.net/config_whitespace.html -->
+ <module name="EmptyForIteratorPad"/>
+ <!-- <module name="GenericWhitespace"/> -->
+ <!-- JEXL-20: because the above is not yet supported through Maven plugin, disable others-->
+ <!-- <module name="NoWhitespaceAfter"/> -->
+ <!-- <module name="NoWhitespaceBefore"/> -->
+ <!-- <module name="WhitespaceAfter"/> -->
+ <!-- <module name="WhitespaceAround"/> -->
+ <module name="OperatorWrap"/>
+ <module name="ParenPad"/>
+ <module name="TypecastParenPad"/>
+ <module name="TabCharacter"/>
+
+
+ <!-- Modifier Checks -->
+ <!-- See http://checkstyle.sf.net/config_modifiers.html -->
+ <module name="ModifierOrder"/>
+ <module name="RedundantModifier"/>
+
+
+ <!-- Checks for blocks. You know, those {}'s -->
+ <!-- See http://checkstyle.sf.net/config_blocks.html -->
+ <module name="AvoidNestedBlocks"/>
+ <module name="EmptyBlock"/>
+ <module name="LeftCurly"/>
+ <module name="NeedBraces"/>
+ <module name="RightCurly"/>
+
+
+ <!-- Checks for common coding problems -->
+ <!-- See http://checkstyle.sf.net/config_coding.html -->
+ <!-- JEXL: module name="AvoidInlineConditionals"/-->
+ <module name="DoubleCheckedLocking"/>
+ <module name="EmptyStatement"/>
+ <module name="EqualsHashCode"/>
+ <module name="HiddenField"/>
+ <module name="IllegalInstantiation"/>
+ <module name="InnerAssignment"/>
+ <module name="MagicNumber">
+ <property name="ignoreNumbers" value="-1, 0, 1, 2, 3"/>
+ </module>
+ <module name="MissingSwitchDefault"/>
+ <!-- JEXL-20: used in introspection -->
+ <!--<module name="RedundantThrows"/>-->
+ <module name="SimplifyBooleanExpression"/>
+ <module name="SimplifyBooleanReturn"/>
+
+ <!-- Checks for class design -->
+ <!-- See http://checkstyle.sf.net/config_design.html -->
+ <!-- JEXL: module name="DesignForExtension"/-->
+ <!-- JEXL: module name="FinalClass"/-->
+ <!-- JEXL: module name="HideUtilityClassConstructor"/-->
+ <module name="InterfaceIsType"/>
+ <module name="VisibilityModifier">
+ <property name="protectedAllowed" value="true"/>
+ </module>
+
+
+ <!-- Miscellaneous other checks. -->
+ <!-- See http://checkstyle.sf.net/config_misc.html -->
+ <module name="ArrayTypeStyle"/>
+ <!-- JEXL: module name="FinalParameters"/-->
+ <!-- JEXL: module name="GenericIllegalRegexp">
+ <property name="format" value="\s+$"/>
+ <property name="message" value="Line has trailing spaces."/>
+ </module-->
+ <!--module name="TodoComment"/-->
+ <module name="UpperEll"/>
+
+ <!-- used for suppression comment filter -->
+ <module name="FileContentsHolder"/>
+ </module>
+
+ <module name="SuppressionCommentFilter">
+ <property name="offCommentFormat" value="CSOFF\: ([\w\|]+)"/>
+ <property name="onCommentFormat" value="CSON\: ([\w\|]+)"/>
+ <property name="checkFormat" value="$1"/>
+ </module>
+
+</module>
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/main/config/header.txt b/COMMONS_JEXL_2_0/src/main/config/header.txt
new file mode 100644
index 0000000..f974c9a
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/config/header.txt
@@ -0,0 +1,16 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/DebugInfo.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/DebugInfo.java
new file mode 100644
index 0000000..4306d0a
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/DebugInfo.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+
+
+/**
+ * Little class to carry in info such as a url/file name, line and column for
+ * information error reporting from the uberspector implementations.
+ * @version $Id$
+ */
+public class DebugInfo implements JexlInfo {
+ /** line number. */
+ private final int line;
+ /** column number. */
+ private final int column;
+ /** name. */
+ private final String name;
+ /**
+ * Create info.
+ * @param tn template name
+ * @param l line number
+ * @param c column
+ */
+ public DebugInfo(String tn, int l, int c) {
+ name = tn;
+ line = l;
+ column = c;
+ }
+
+ /**
+ * Formats this info in the form 'name@line:column'.
+ * @return the formatted info
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(name != null? name : "");
+ if (line > 0) {
+ sb.append("@");
+ sb.append(line);
+ if (column > 0) {
+ sb.append(":");
+ sb.append(column);
+ }
+ }
+ return sb.toString();
+ }
+
+ /** {@inheritDoc} */
+ public String debugString() {
+ return toString();
+ }
+
+ /**
+ * Gets the file/script/url name.
+ * @return template name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Gets the line number.
+ * @return line number.
+ */
+ public int getLine() {
+ return line;
+ }
+
+ /**
+ * Gets the column number.
+ * @return the column.
+ */
+ public int getColumn() {
+ return column;
+ }
+}
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/Debugger.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/Debugger.java
new file mode 100644
index 0000000..4048e4b
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/Debugger.java
@@ -0,0 +1,641 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+
+import org.apache.commons.jexl2.parser.ASTAdditiveNode;
+import org.apache.commons.jexl2.parser.ASTAdditiveOperator;
+import org.apache.commons.jexl2.parser.ASTAndNode;
+import org.apache.commons.jexl2.parser.ASTAmbiguous;
+import org.apache.commons.jexl2.parser.ASTArrayAccess;
+import org.apache.commons.jexl2.parser.ASTArrayLiteral;
+import org.apache.commons.jexl2.parser.ASTAssignment;
+import org.apache.commons.jexl2.parser.ASTBitwiseAndNode;
+import org.apache.commons.jexl2.parser.ASTBitwiseComplNode;
+import org.apache.commons.jexl2.parser.ASTBitwiseOrNode;
+import org.apache.commons.jexl2.parser.ASTBitwiseXorNode;
+import org.apache.commons.jexl2.parser.ASTBlock;
+import org.apache.commons.jexl2.parser.ASTConstructorNode;
+import org.apache.commons.jexl2.parser.ASTDivNode;
+import org.apache.commons.jexl2.parser.ASTEQNode;
+import org.apache.commons.jexl2.parser.ASTERNode;
+import org.apache.commons.jexl2.parser.ASTEmptyFunction;
+import org.apache.commons.jexl2.parser.ASTFalseNode;
+import org.apache.commons.jexl2.parser.ASTFloatLiteral;
+import org.apache.commons.jexl2.parser.ASTForeachStatement;
+import org.apache.commons.jexl2.parser.ASTFunctionNode;
+import org.apache.commons.jexl2.parser.ASTGENode;
+import org.apache.commons.jexl2.parser.ASTGTNode;
+import org.apache.commons.jexl2.parser.ASTIdentifier;
+import org.apache.commons.jexl2.parser.ASTIfStatement;
+import org.apache.commons.jexl2.parser.ASTIntegerLiteral;
+import org.apache.commons.jexl2.parser.ASTJexlScript;
+import org.apache.commons.jexl2.parser.ASTLENode;
+import org.apache.commons.jexl2.parser.ASTLTNode;
+import org.apache.commons.jexl2.parser.ASTMapEntry;
+import org.apache.commons.jexl2.parser.ASTMapLiteral;
+import org.apache.commons.jexl2.parser.ASTMethodNode;
+import org.apache.commons.jexl2.parser.ASTModNode;
+import org.apache.commons.jexl2.parser.ASTMulNode;
+import org.apache.commons.jexl2.parser.ASTNENode;
+import org.apache.commons.jexl2.parser.ASTNRNode;
+import org.apache.commons.jexl2.parser.ASTNotNode;
+import org.apache.commons.jexl2.parser.ASTNullLiteral;
+import org.apache.commons.jexl2.parser.ASTOrNode;
+import org.apache.commons.jexl2.parser.ASTReference;
+import org.apache.commons.jexl2.parser.ASTSizeFunction;
+import org.apache.commons.jexl2.parser.ASTSizeMethod;
+import org.apache.commons.jexl2.parser.ASTStringLiteral;
+import org.apache.commons.jexl2.parser.ASTTernaryNode;
+import org.apache.commons.jexl2.parser.ASTTrueNode;
+import org.apache.commons.jexl2.parser.ASTUnaryMinusNode;
+import org.apache.commons.jexl2.parser.ASTWhileStatement;
+import org.apache.commons.jexl2.parser.JexlNode;
+import org.apache.commons.jexl2.parser.SimpleNode;
+
+import org.apache.commons.jexl2.parser.ParserVisitor;
+
+/**
+ * Helps pinpoint the cause of problems in expressions that fail during evaluation.
+ * <p>
+ * It rebuilds an expression string from the tree and the start/end offsets of the cause
+ * in that string.
+ * </p>
+ * This implies that exceptions during evaluation do allways carry the node that's causing
+ * the error.
+ * @since 2.0
+ */
+final class Debugger implements ParserVisitor {
+ /** The builder to compose messages. */
+ private final StringBuilder builder;
+ /** The cause of the issue to debug. */
+ private JexlNode cause;
+ /** The starting character location offset of the cause in the builder. */
+ private int start;
+ /** The ending character location offset of the cause in the builder. */
+ private int end;
+
+ /**
+ * Creates a Debugger.
+ */
+ Debugger() {
+ builder = new StringBuilder();
+ cause = null;
+ start = 0;
+ end = 0;
+ }
+
+ /**
+ * Seeks the location of an error cause (a node) in an expression.
+ * @param node the node to debug
+ * @return true if the cause was located, false otherwise
+ */
+ public boolean debug(JexlNode node) {
+ start = 0;
+ end = 0;
+ if (node != null) {
+ builder.setLength(0);
+ this.cause = node;
+ // make arg cause become the root cause
+ JexlNode root = node;
+ while (root.jjtGetParent() != null) {
+ root = root.jjtGetParent();
+ }
+ root.jjtAccept(this, null);
+ }
+ return end > 0;
+ }
+
+ /**
+ * @return The rebuilt expression
+ */
+ public String data() {
+ return builder.toString();
+ }
+
+ /**
+ * @return The starting offset location of the cause in the expression
+ */
+ public int start() {
+ return start;
+ }
+
+ /**
+ * @return The end offset location of the cause in the expression
+ */
+ public int end() {
+ return end;
+ }
+
+ /**
+ * Checks if a child node is the cause to debug & adds its representation
+ * to the rebuilt expression.
+ * @param node the child node
+ * @param data visitor pattern argument
+ * @return visitor pattern value
+ */
+ private Object accept(JexlNode node, Object data) {
+ if (node == cause) {
+ start = builder.length();
+ }
+ Object value = node.jjtAccept(this, data);
+ if (node == cause) {
+ end = builder.length();
+ }
+ return value;
+ }
+
+ /**
+ * Adds a statement node to the rebuilt expression.
+ * @param child the child node
+ * @param data visitor pattern argument
+ * @return visitor pattern value
+ */
+ private Object acceptStatement(JexlNode child, Object data) {
+ Object value = accept(child, data);
+ // blocks, if, for & while dont need a ';' at end
+ if (child instanceof ASTBlock
+ || child instanceof ASTIfStatement
+ || child instanceof ASTForeachStatement
+ || child instanceof ASTWhileStatement) {
+ return value;
+ }
+ builder.append(";");
+ return value;
+ }
+
+ /**
+ * Checks if a terminal node is the the cause to debug & adds its
+ * representation to the rebuilt expression.
+ * @param node the child node
+ * @param image the child node token image (may be null)
+ * @param data visitor pattern argument
+ * @return visitor pattern value
+ */
+ private Object check(JexlNode node, String image, Object data) {
+ if (node == cause) {
+ start = builder.length();
+ }
+ if (image != null) {
+ builder.append(image);
+ } else {
+ builder.append(node.toString());
+ }
+ if (node == cause) {
+ end = builder.length();
+ }
+ return data;
+ }
+
+ /**
+ * Checks if the children of a node using infix notation is the cause to debug,
+ * adds their representation to the rebuilt expression.
+ * @param node the child node
+ * @param infix the child node token
+ * @param paren whether the child should be parenthesized
+ * @param data visitor pattern argument
+ * @return visitor pattern value
+ */
+ private Object infixChildren(JexlNode node, String infix, boolean paren, Object data) {
+ int num = node.jjtGetNumChildren(); //child.jjtGetNumChildren() > 1;
+ if (paren) {
+ builder.append("(");
+ }
+ for (int i = 0; i < num; ++i) {
+ if (i > 0) {
+ builder.append(infix);
+ }
+ accept(node.jjtGetChild(i), data);
+ }
+ if (paren) {
+ builder.append(")");
+ }
+ return data;
+ }
+
+ /**
+ * Checks if the child of a node using prefix notation is the cause to debug,
+ * adds their representation to the rebuilt expression.
+ * @param node the node
+ * @param prefix the node token
+ * @param data visitor pattern argument
+ * @return visitor pattern value
+ */
+ private Object prefixChild(JexlNode node, String prefix, Object data) {
+ boolean paren = node.jjtGetChild(0).jjtGetNumChildren() > 1;
+ builder.append(prefix);
+ if (paren) {
+ builder.append("(");
+ }
+ accept(node.jjtGetChild(0), data);
+ if (paren) {
+ builder.append(")");
+ }
+ return data;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTAdditiveNode node, Object data) {
+ // need parenthesis if not in operator precedence order
+ boolean paren = node.jjtGetParent() instanceof ASTMulNode
+ || node.jjtGetParent() instanceof ASTDivNode
+ || node.jjtGetParent() instanceof ASTModNode;
+ int num = node.jjtGetNumChildren(); //child.jjtGetNumChildren() > 1;
+ if (paren) {
+ builder.append("(");
+ }
+ accept(node.jjtGetChild(0), data);
+ for (int i = 1; i < num; ++i) {
+ accept(node.jjtGetChild(i), data);
+ }
+ if (paren) {
+ builder.append(")");
+ }
+ return data;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTAdditiveOperator node, Object data) {
+ builder.append(' ');
+ builder.append(node.image);
+ builder.append(' ');
+ return data;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTAndNode node, Object data) {
+ return infixChildren(node, " && ", false, data);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTArrayAccess node, Object data) {
+ accept(node.jjtGetChild(0), data);
+ int num = node.jjtGetNumChildren();
+ for (int i = 1; i < num; ++i) {
+ builder.append("[");
+ accept(node.jjtGetChild(i), data);
+ builder.append("]");
+ }
+ return data;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTArrayLiteral node, Object data) {
+ int num = node.jjtGetNumChildren();
+ builder.append("[ ");
+ accept(node.jjtGetChild(0), data);
+ for (int i = 1; i < num; ++i) {
+ builder.append(", ");
+ accept(node.jjtGetChild(i), data);
+ }
+ builder.append(" ]");
+ return data;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTAssignment node, Object data) {
+ return infixChildren(node, " = ", false, data);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTBitwiseAndNode node, Object data) {
+ return infixChildren(node, " & ", false, data);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTBitwiseComplNode node, Object data) {
+ return prefixChild(node, "~", data);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTBitwiseOrNode node, Object data) {
+ boolean paren = node.jjtGetParent() instanceof ASTBitwiseAndNode;
+ return infixChildren(node, " | ", paren, data);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTBitwiseXorNode node, Object data) {
+ boolean paren = node.jjtGetParent() instanceof ASTBitwiseAndNode;
+ return infixChildren(node, " ^ ", paren, data);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTBlock node, Object data) {
+ builder.append("{ ");
+ int num = node.jjtGetNumChildren();
+ for (int i = 0; i < num; ++i) {
+ JexlNode child = node.jjtGetChild(i);
+ acceptStatement(child, data);
+ }
+ builder.append(" }");
+ return data;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTDivNode node, Object data) {
+ return infixChildren(node, " / ", false, data);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTEmptyFunction node, Object data) {
+ builder.append("empty(");
+ accept(node.jjtGetChild(0), data);
+ builder.append(")");
+ return data;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTEQNode node, Object data) {
+ return infixChildren(node, " == ", false, data);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTERNode node, Object data) {
+ return infixChildren(node, " =~ ", false, data);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTFalseNode node, Object data) {
+ return check(node, "false", data);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTFloatLiteral node, Object data) {
+ return check(node, node.image, data);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTForeachStatement node, Object data) {
+ builder.append("for(");
+ accept(node.jjtGetChild(0), data);
+ builder.append(" : ");
+ accept(node.jjtGetChild(1), data);
+ builder.append(") ");
+ if (node.jjtGetNumChildren() > 2) {
+ acceptStatement(node.jjtGetChild(2), data);
+ } else {
+ builder.append(';');
+ }
+ return data;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTGENode node, Object data) {
+ return infixChildren(node, " >= ", false, data);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTGTNode node, Object data) {
+ return infixChildren(node, " > ", false, data);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTIdentifier node, Object data) {
+ return check(node, node.image, data);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTIfStatement node, Object data) {
+ builder.append("if (");
+ accept(node.jjtGetChild(0), data);
+ builder.append(") ");
+ if (node.jjtGetNumChildren() > 1) {
+ acceptStatement(node.jjtGetChild(1), data);
+ if (node.jjtGetNumChildren() > 2) {
+ builder.append(" else ");
+ acceptStatement(node.jjtGetChild(2), data);
+ } else {
+ builder.append(';');
+ }
+ } else {
+ builder.append(';');
+ }
+ return data;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTIntegerLiteral node, Object data) {
+ return check(node, node.image, data);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTJexlScript node, Object data) {
+ int num = node.jjtGetNumChildren();
+ for (int i = 0; i < num; ++i) {
+ JexlNode child = node.jjtGetChild(i);
+ acceptStatement(child, data);
+ }
+ return data;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTLENode node, Object data) {
+ return infixChildren(node, " <= ", false, data);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTLTNode node, Object data) {
+ return infixChildren(node, " < ", false, data);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTMapEntry node, Object data) {
+ accept(node.jjtGetChild(0), data);
+ builder.append(" : ");
+ accept(node.jjtGetChild(1), data);
+ return data;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTMapLiteral node, Object data) {
+ int num = node.jjtGetNumChildren();
+ builder.append("{ ");
+ accept(node.jjtGetChild(0), data);
+ for (int i = 1; i < num; ++i) {
+ builder.append(", ");
+ accept(node.jjtGetChild(i), data);
+ }
+ builder.append(" }");
+ return data;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTConstructorNode node, Object data) {
+ int num = node.jjtGetNumChildren();
+ builder.append("new ");
+ builder.append("(");
+ accept(node.jjtGetChild(0), data);
+ for (int i = 1; i < num; ++i) {
+ builder.append(", ");
+ accept(node.jjtGetChild(i), data);
+ }
+ builder.append(")");
+ return data;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTFunctionNode node, Object data) {
+ int num = node.jjtGetNumChildren();
+ accept(node.jjtGetChild(0), data);
+ builder.append(":");
+ accept(node.jjtGetChild(1), data);
+ builder.append("(");
+ for (int i = 2; i < num; ++i) {
+ if (i > 2) {
+ builder.append(", ");
+ }
+ accept(node.jjtGetChild(i), data);
+ }
+ builder.append(")");
+ return data;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTMethodNode node, Object data) {
+ int num = node.jjtGetNumChildren();
+ accept(node.jjtGetChild(0), data);
+ builder.append("(");
+ for (int i = 1; i < num; ++i) {
+ if (i > 1) {
+ builder.append(", ");
+ }
+ accept(node.jjtGetChild(i), data);
+ }
+ builder.append(")");
+ return data;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTModNode node, Object data) {
+ return infixChildren(node, " % ", false, data);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTMulNode node, Object data) {
+ return infixChildren(node, " * ", false, data);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTNENode node, Object data) {
+ return infixChildren(node, " != ", false, data);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTNRNode node, Object data) {
+ return infixChildren(node, " !~ ", false, data);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTNotNode node, Object data) {
+ builder.append("!");
+ accept(node.jjtGetChild(0), data);
+ return data;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTNullLiteral node, Object data) {
+ check(node, "null", data);
+ return data;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTOrNode node, Object data) {
+ // need parenthesis if not in operator precedence order
+ boolean paren = node.jjtGetParent() instanceof ASTAndNode;
+ return infixChildren(node, " || ", paren, data);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTReference node, Object data) {
+ int num = node.jjtGetNumChildren();
+ accept(node.jjtGetChild(0), data);
+ for (int i = 1; i < num; ++i) {
+ builder.append(".");
+ accept(node.jjtGetChild(i), data);
+ }
+ return data;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTSizeFunction node, Object data) {
+ builder.append("size(");
+ accept(node.jjtGetChild(0), data);
+ builder.append(")");
+ return data;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTSizeMethod node, Object data) {
+ check(node, "size()", data);
+ return data;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTStringLiteral node, Object data) {
+ String img = node.image.replace("'", "\\'");
+ return check(node, "'" + img + "'", data);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTTernaryNode node, Object data) {
+ accept(node.jjtGetChild(0), data);
+ if (node.jjtGetNumChildren() > 2) {
+ builder.append("? ");
+ accept(node.jjtGetChild(1), data);
+ builder.append(" : ");
+ accept(node.jjtGetChild(2), data);
+ } else {
+ builder.append("?:");
+ accept(node.jjtGetChild(1), data);
+
+ }
+ return data;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTTrueNode node, Object data) {
+ check(node, "true", data);
+ return data;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTUnaryMinusNode node, Object data) {
+ return prefixChild(node, "-", data);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTWhileStatement node, Object data) {
+ builder.append("while (");
+ accept(node.jjtGetChild(0), data);
+ builder.append(") ");
+ if (node.jjtGetNumChildren() > 1) {
+ acceptStatement(node.jjtGetChild(1), data);
+ } else {
+ builder.append(';');
+ }
+ return data;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(SimpleNode node, Object data) {
+ throw new UnsupportedOperationException("unexpected type of node");
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTAmbiguous node, Object data) {
+ throw new UnsupportedOperationException("unexpected type of node");
+ }
+}
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/Expression.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/Expression.java
new file mode 100644
index 0000000..3966d77
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/Expression.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2;
+
+
+/**
+ * Represents a single JEXL expression.
+ * <p>
+ * This simple interface provides access to the underlying expression through
+ * {@link Expression#getExpression()}.
+ * </p>
+ *
+ * <p>
+ * An expression is different than a script - it is simply a reference of
+ * an expression.
+ * </p>
+ *
+ * @since 1.0
+ * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public interface Expression {
+ /**
+ * Evaluates the expression with the variables contained in the
+ * supplied {@link JexlContext}.
+ *
+ * @param context A JexlContext containing variables.
+ * @return The result of this evaluation
+ * @throws JexlException on any error
+ */
+ Object evaluate(JexlContext context);
+
+ /**
+ * Returns the JEXL expression this Expression was created with.
+ *
+ * @return The JEXL expression to be evaluated
+ */
+ String getExpression();
+
+ /**
+ * Returns the JEXL expression by reconstructing it from the parsed tree.
+ * @return the JEXL expression
+ */
+ String dump();
+}
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/ExpressionImpl.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/ExpressionImpl.java
new file mode 100644
index 0000000..63f653e
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/ExpressionImpl.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2;
+
+import org.apache.commons.jexl2.parser.ASTJexlScript;
+
+/**
+ * Instances of ExpressionImpl are created by the {@link JexlEngine},
+ * and this is the default implementation of the {@link Expression} and
+ * {@link Script} interface.
+ * @since 1.0
+ * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class ExpressionImpl implements Expression, Script {
+ /** The engine for this expression. */
+ protected final JexlEngine jexl;
+ /**
+ * Original expression stripped from leading & trailing spaces.
+ */
+ protected final String expression;
+ /**
+ * The resulting AST we can interpret.
+ */
+ protected final ASTJexlScript script;
+
+
+ /**
+ * Do not let this be generally instantiated with a 'new'.
+ *
+ * @param engine the interpreter to evaluate the expression
+ * @param expr the expression.
+ * @param ref the parsed expression.
+ */
+ protected ExpressionImpl(JexlEngine engine, String expr, ASTJexlScript ref) {
+ jexl = engine;
+ expression = expr;
+ script = ref;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object evaluate(JexlContext context) {
+ if (script.jjtGetNumChildren() < 1) {
+ return null;
+ }
+ Interpreter interpreter = jexl.createInterpreter(context);
+ return interpreter.interpret(script.jjtGetChild(0));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String dump() {
+ Debugger debug = new Debugger();
+ return debug.debug(script)? debug.toString() : "/*?*/";
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getExpression() {
+ return expression;
+ }
+
+ /**
+ * Provide a string representation of the expression.
+ *
+ * @return the expression or blank if it's null.
+ */
+ @Override
+ public String toString() {
+ String expr = getExpression();
+ return expr == null ? "" : expr;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getText() {
+ return toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object execute(JexlContext context) {
+ Interpreter interpreter = jexl.createInterpreter(context);
+ return interpreter.interpret(script);
+ }
+
+}
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/Interpreter.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/Interpreter.java
new file mode 100644
index 0000000..d82f2bd
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/Interpreter.java
@@ -0,0 +1,1349 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.commons.jexl2.parser.SimpleNode;
+import org.apache.commons.logging.Log;
+
+import org.apache.commons.jexl2.parser.JexlNode;
+import org.apache.commons.jexl2.parser.ASTAdditiveNode;
+import org.apache.commons.jexl2.parser.ASTAdditiveOperator;
+import org.apache.commons.jexl2.parser.ASTAndNode;
+import org.apache.commons.jexl2.parser.ASTAmbiguous;
+import org.apache.commons.jexl2.parser.ASTArrayAccess;
+import org.apache.commons.jexl2.parser.ASTArrayLiteral;
+import org.apache.commons.jexl2.parser.ASTAssignment;
+import org.apache.commons.jexl2.parser.ASTBitwiseAndNode;
+import org.apache.commons.jexl2.parser.ASTBitwiseComplNode;
+import org.apache.commons.jexl2.parser.ASTBitwiseOrNode;
+import org.apache.commons.jexl2.parser.ASTBitwiseXorNode;
+import org.apache.commons.jexl2.parser.ASTBlock;
+import org.apache.commons.jexl2.parser.ASTConstructorNode;
+import org.apache.commons.jexl2.parser.ASTDivNode;
+import org.apache.commons.jexl2.parser.ASTEQNode;
+import org.apache.commons.jexl2.parser.ASTERNode;
+import org.apache.commons.jexl2.parser.ASTEmptyFunction;
+import org.apache.commons.jexl2.parser.ASTFalseNode;
+import org.apache.commons.jexl2.parser.ASTFunctionNode;
+import org.apache.commons.jexl2.parser.ASTFloatLiteral;
+import org.apache.commons.jexl2.parser.ASTForeachStatement;
+import org.apache.commons.jexl2.parser.ASTGENode;
+import org.apache.commons.jexl2.parser.ASTGTNode;
+import org.apache.commons.jexl2.parser.ASTIdentifier;
+import org.apache.commons.jexl2.parser.ASTIfStatement;
+import org.apache.commons.jexl2.parser.ASTIntegerLiteral;
+import org.apache.commons.jexl2.parser.ASTJexlScript;
+import org.apache.commons.jexl2.parser.ASTLENode;
+import org.apache.commons.jexl2.parser.ASTLTNode;
+import org.apache.commons.jexl2.parser.ASTMapEntry;
+import org.apache.commons.jexl2.parser.ASTMapLiteral;
+import org.apache.commons.jexl2.parser.ASTMethodNode;
+import org.apache.commons.jexl2.parser.ASTModNode;
+import org.apache.commons.jexl2.parser.ASTMulNode;
+import org.apache.commons.jexl2.parser.ASTNENode;
+import org.apache.commons.jexl2.parser.ASTNRNode;
+import org.apache.commons.jexl2.parser.ASTNotNode;
+import org.apache.commons.jexl2.parser.ASTNullLiteral;
+import org.apache.commons.jexl2.parser.ASTOrNode;
+import org.apache.commons.jexl2.parser.ASTReference;
+import org.apache.commons.jexl2.parser.ASTSizeFunction;
+import org.apache.commons.jexl2.parser.ASTSizeMethod;
+import org.apache.commons.jexl2.parser.ASTStringLiteral;
+import org.apache.commons.jexl2.parser.ASTTernaryNode;
+import org.apache.commons.jexl2.parser.ASTTrueNode;
+import org.apache.commons.jexl2.parser.ASTUnaryMinusNode;
+import org.apache.commons.jexl2.parser.ASTWhileStatement;
+import org.apache.commons.jexl2.parser.Node;
+import org.apache.commons.jexl2.parser.ParserVisitor;
+
+import org.apache.commons.jexl2.introspection.Uberspect;
+import org.apache.commons.jexl2.introspection.JexlMethod;
+import org.apache.commons.jexl2.introspection.JexlPropertyGet;
+import org.apache.commons.jexl2.introspection.JexlPropertySet;
+
+/**
+ * An interpreter of JEXL syntax.
+ *
+ * @since 2.0
+ */
+public class Interpreter implements ParserVisitor {
+ /** The logger. */
+ protected final Log logger;
+ /** The uberspect. */
+ protected final Uberspect uberspect;
+ /** The arithmetic handler. */
+ protected final JexlArithmetic arithmetic;
+ /** The map of registered functions. */
+ protected final Map<String, Object> functions;
+ /** The map of registered functions. */
+ protected Map<String, Object> functors;
+ /** The context to store/retrieve variables. */
+ protected final JexlContext context;
+ /** Strict interpreter flag. */
+ protected final boolean strict;
+ /** Silent intepreter flag. */
+ protected boolean silent;
+ /** Cache executors. */
+ protected final boolean cache;
+ /** Registers made of 2 pairs of {register-name, value}. */
+ protected Object[] registers = null;
+ /** Empty parameters for method matching. */
+ protected static final Object[] EMPTY_PARAMS = new Object[0];
+
+ /**
+ * Creates an interpreter.
+ * @param jexl the engine creating this interpreter
+ * @param aContext the context to evaluate expression
+ */
+ public Interpreter(JexlEngine jexl, JexlContext aContext) {
+ this.logger = jexl.logger;
+ this.uberspect = jexl.uberspect;
+ this.arithmetic = jexl.arithmetic;
+ this.functions = jexl.functions;
+ this.strict = !this.arithmetic.isLenient();
+ this.silent = jexl.silent;
+ this.cache = jexl.cache != null;
+ this.context = aContext;
+ this.functors = null;
+ }
+
+ /**
+ * Sets whether this interpreter throws JexlException during evaluation.
+ * @param flag true means no JexlException will be thrown but will be logged
+ * as info through the Jexl engine logger, false allows them to be thrown.
+ */
+ public void setSilent(boolean flag) {
+ this.silent = flag;
+ }
+
+ /**
+ * Checks whether this interpreter throws JexlException during evaluation.
+ * @return true if silent, false otherwise
+ */
+ public boolean isSilent() {
+ return this.silent;
+ }
+
+ /**
+ * Interpret the given script/expression.
+ * <p>
+ * If the underlying JEXL engine is silent, errors will be logged through its logger as info.
+ * </p>
+ * @param node the script or expression to interpret.
+ * @return the result of the interpretation.
+ * @throws JexlException if any error occurs during interpretation.
+ */
+ public Object interpret(JexlNode node) {
+ try {
+ return node.jjtAccept(this, null);
+ } catch (JexlException xjexl) {
+ if (silent) {
+ logger.warn(xjexl.getMessage(), xjexl.getCause());
+ return null;
+ }
+ throw xjexl;
+ }
+ }
+
+ /**
+ * Gets the uberspect.
+ * @return an {@link Uberspect}
+ */
+ protected Uberspect getUberspect() {
+ return uberspect;
+ }
+
+ /**
+ * Sets this interpreter registers for bean access/assign expressions.
+ * @param theRegisters the array of registers
+ */
+ protected void setRegisters(Object[] theRegisters) {
+ this.registers = theRegisters;
+ }
+
+ /**
+ * Finds the node causing a NPE for diadic operators.
+ * @param xrt the RuntimeException
+ * @param node the parent node
+ * @param left the left argument
+ * @param right the right argument
+ * @return the left, right or parent node
+ */
+ protected JexlNode findNullOperand(RuntimeException xrt, JexlNode node, Object left, Object right) {
+ if (xrt instanceof NullPointerException
+ && JexlException.NULL_OPERAND == xrt.getMessage()) {
+ if (left == null) {
+ return node.jjtGetChild(0);
+ }
+ if (right == null) {
+ return node.jjtGetChild(1);
+ }
+ }
+ return node;
+ }
+
+ /**
+ * Triggered when variable can not be resolved.
+ * @param xjexl the JexlException ("undefined variable " + variable)
+ * @return throws JexlException if strict, null otherwise
+ */
+ protected Object unknownVariable(JexlException xjexl) {
+ if (strict) {
+ throw xjexl;
+ }
+ if (!silent) {
+ logger.warn(xjexl.getMessage());
+ }
+ return null;
+ }
+
+ /**
+ * Triggered when method, function or constructor invocation fails.
+ * @param xjexl the JexlException wrapping the original error
+ * @return throws JexlException if strict, null otherwise
+ */
+ protected Object invocationFailed(JexlException xjexl) {
+ if (strict) {
+ throw xjexl;
+ }
+ if (!silent) {
+ logger.warn(xjexl.getMessage(), xjexl.getCause());
+ }
+ return null;
+ }
+
+ /**
+ * Resolves a namespace, eventually allocating an instance using context as constructor argument.
+ * The lifetime of such instances span the current expression or script evaluation.
+ *
+ * @param prefix the prefix name (may be null for global namespace)
+ * @param node the AST node
+ * @return the namespace instance
+ */
+ protected Object resolveNamespace(String prefix, JexlNode node) {
+ Object namespace;
+ // check whether this namespace is a functor
+ if (functors != null) {
+ namespace = functors.get(prefix);
+ if (namespace != null) {
+ return namespace;
+ }
+ }
+ namespace = functions.get(prefix);
+ if (namespace == null) {
+ throw new JexlException(node, "no such function namespace " + prefix);
+ }
+ // allow namespace to be instantiated as functor with context
+ if (namespace instanceof Class<?>) {
+ Object[] args = new Object[]{context};
+ Constructor<?> ctor = uberspect.getConstructor(namespace,args, node);
+ if (ctor != null) {
+ try {
+ namespace = ctor.newInstance(args);
+ if (functors == null) {
+ functors = new HashMap<String, Object>();
+ }
+ functors.put(prefix, namespace);
+ } catch (Exception xinst) {
+ throw new JexlException(node, "unable to instantiate namespace " + prefix, xinst);
+ }
+ }
+ }
+ return namespace;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTAdditiveNode node, Object data) {
+ /**
+ * The pattern for exception mgmt is to let the child*.jjtAccept
+ * out of the try/catch loop so that if one fails, the ex will
+ * traverse up to the interpreter.
+ * In cases where this is not convenient/possible, JexlException must
+ * be caught explicitly and rethrown.
+ */
+ Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ for(int c = 2, size = node.jjtGetNumChildren(); c < size; c += 2) {
+ Object right = node.jjtGetChild(c).jjtAccept(this, data);
+ try {
+ JexlNode op = node.jjtGetChild(c - 1);
+ if (op instanceof ASTAdditiveOperator) {
+ String which = ((ASTAdditiveOperator) op).image;
+ if ("+".equals(which)) {
+ left = arithmetic.add(left, right);
+ continue;
+ }
+ if ("-".equals(which)) {
+ left = arithmetic.subtract(left, right);
+ continue;
+ }
+ throw new UnsupportedOperationException("unknown operator " + which);
+ }
+ throw new IllegalArgumentException("unknown operator " + op);
+ } catch (RuntimeException xrt) {
+ JexlNode xnode = findNullOperand(xrt, node, left, right);
+ throw new JexlException(xnode, "+/- error", xrt);
+ }
+ }
+ return left;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTAdditiveOperator node, Object data) {
+ throw new UnsupportedOperationException("Shoud not be called.");
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTAndNode node, Object data) {
+ Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ try {
+ boolean leftValue = arithmetic.toBoolean(left);
+ if (!leftValue) {
+ return Boolean.FALSE;
+ }
+ } catch (RuntimeException xrt) {
+ throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt);
+ }
+ Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ boolean rightValue = arithmetic.toBoolean(right);
+ if (!rightValue) {
+ return Boolean.FALSE;
+ }
+ } catch (RuntimeException xrt) {
+ throw new JexlException(node.jjtGetChild(1), "boolean coercion error", xrt);
+ }
+ return Boolean.TRUE;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTArrayAccess node, Object data) {
+ // first objectNode is the identifier
+ Object object = node.jjtGetChild(0).jjtAccept(this, data);
+ // can have multiple nodes - either an expression, integer literal or
+ // reference
+ int numChildren = node.jjtGetNumChildren();
+ for (int i = 1; i < numChildren; i++) {
+ JexlNode nindex = node.jjtGetChild(i);
+ Object index = nindex.jjtAccept(this, null);
+ object = getAttribute(object, index, nindex);
+ }
+
+ return object;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTArrayLiteral node, Object data) {
+ int childCount = node.jjtGetNumChildren();
+ Object[] array = new Object[childCount];
+ for (int i = 0; i < childCount; i++) {
+ Object entry = node.jjtGetChild(i).jjtAccept(this, data);
+ array[i] = entry;
+ }
+ return arithmetic.narrowArrayType(array);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTAssignment node, Object data) {
+ // left contains the reference to assign to
+ JexlNode left = node.jjtGetChild(0);
+ if (!(left instanceof ASTReference)) {
+ throw new JexlException(left, "illegal assignment form");
+ }
+ // right is the value expression to assign
+ Object right = node.jjtGetChild(1).jjtAccept(this, data);
+
+ // determine initial object & property:
+ JexlNode objectNode = null;
+ Object object = null;
+ JexlNode propertyNode = null;
+ Object property = null;
+ boolean isVariable = true;
+ int v = 0;
+ StringBuilder variableName = null;
+ // 1: follow children till penultimate
+ int last = left.jjtGetNumChildren() - 1;
+ for (int c = 0; c < last; ++c) {
+ objectNode = left.jjtGetChild(c);
+ // evaluate the property within the object
+ object = objectNode.jjtAccept(this, object);
+ if (object != null) {
+ continue;
+ }
+ isVariable &= objectNode instanceof ASTIdentifier;
+ // if we get null back as a result, check for an ant variable
+ if (isVariable) {
+ if (v == 0) {
+ variableName = new StringBuilder(left.jjtGetChild(0).image);
+ v = 1;
+ }
+ for(; v <= c; ++v) {
+ variableName.append('.');
+ variableName.append(left.jjtGetChild(v).image);
+ }
+ object = context.get(variableName.toString());
+ // disallow mixing ant & bean with same root; avoid ambiguity
+ if (object != null) {
+ isVariable = false;
+ }
+ } else {
+ throw new JexlException(objectNode, "illegal assignment form");
+ }
+ }
+ // 2: last objectNode will perform assignement in all cases
+ propertyNode = left.jjtGetChild(last);
+ if (propertyNode instanceof ASTIdentifier) {
+ property = ((ASTIdentifier) propertyNode).image;
+ // deal with ant variable
+ if (isVariable && object == null) {
+ if (variableName != null) {
+ if (last > 0) {
+ variableName.append('.');
+ }
+ variableName.append(property);
+ property = variableName.toString();
+ }
+ context.set(String.valueOf(property), right);
+ return right;
+ }
+ } else if (propertyNode instanceof ASTIntegerLiteral) {
+ property = visit((ASTIntegerLiteral) propertyNode, null);
+ // deal with ant variable
+ if (isVariable && object == null) {
+ if (variableName != null) {
+ if (last > 0) {
+ variableName.append('.');
+ }
+ variableName.append(property);
+ property = variableName.toString();
+ }
+ context.set(String.valueOf(property), right);
+ return right;
+ }
+ } else if (propertyNode instanceof ASTArrayAccess) {
+ // first objectNode is the identifier
+ objectNode = propertyNode;
+ ASTArrayAccess narray = (ASTArrayAccess) objectNode;
+ Object nobject = narray.jjtGetChild(0).jjtAccept(this, object);
+ if (nobject == null) {
+ throw new JexlException(objectNode, "array element is null");
+ } else {
+ object = nobject;
+ }
+ // can have multiple nodes - either an expression, integer literal or
+ // reference
+ last = narray.jjtGetNumChildren() - 1;
+ for (int i = 1; i < last; i++) {
+ objectNode = narray.jjtGetChild(i);
+ Object index = objectNode.jjtAccept(this, null);
+ object = getAttribute(object, index, objectNode);
+ }
+ property = narray.jjtGetChild(last).jjtAccept(this, null);
+ } else {
+ throw new JexlException(objectNode, "illegal assignment form");
+ }
+ if (property == null) {
+ // no property, we fail
+ throw new JexlException(propertyNode, "property is null");
+ }
+ if (object == null) {
+ // no object, we fail
+ throw new JexlException(objectNode, "bean is null");
+ }
+ // one before last, assign
+ setAttribute(object, property, right, propertyNode);
+ return right;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTBitwiseAndNode node, Object data) {
+ Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ int n = 0;
+ // coerce these two values longs and 'and'.
+ try {
+ long l = arithmetic.toLong(left);
+ n = 1;
+ long r = arithmetic.toLong(right);
+ return Long.valueOf(l & r);
+ } catch (RuntimeException xrt) {
+ throw new JexlException(node.jjtGetChild(n), "long coercion error", xrt);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTBitwiseComplNode node, Object data) {
+ Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ try {
+ long l = arithmetic.toLong(left);
+ return Long.valueOf(~l);
+ } catch (RuntimeException xrt) {
+ throw new JexlException(node.jjtGetChild(0), "long coercion error", xrt);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTBitwiseOrNode node, Object data) {
+ Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ int n = 0;
+ // coerce these two values longs and 'or'.
+ try {
+ long l = arithmetic.toLong(left);
+ n = 1;
+ long r = arithmetic.toLong(right);
+ return Long.valueOf(l | r);
+ } catch (RuntimeException xrt) {
+ throw new JexlException(node.jjtGetChild(n), "long coercion error", xrt);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTBitwiseXorNode node, Object data) {
+ Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ int n = 0;
+ // coerce these two values longs and 'xor'.
+ try {
+ long l = arithmetic.toLong(left);
+ n = 1;
+ long r = arithmetic.toLong(right);
+ return Long.valueOf(l ^ r);
+ } catch (RuntimeException xrt) {
+ throw new JexlException(node.jjtGetChild(n), "long coercion error", xrt);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTBlock node, Object data) {
+ int numChildren = node.jjtGetNumChildren();
+ Object result = null;
+ for (int i = 0; i < numChildren; i++) {
+ result = node.jjtGetChild(i).jjtAccept(this, data);
+ }
+ return result;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTDivNode node, Object data) {
+ Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ return arithmetic.divide(left, right);
+ } catch (RuntimeException xrt) {
+ if (!strict && xrt instanceof ArithmeticException) {
+ return new Double(0.0);
+ }
+ JexlNode xnode = findNullOperand(xrt, node, left, right);
+ throw new JexlException(xnode, "divide error", xrt);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTEmptyFunction node, Object data) {
+ Object o = node.jjtGetChild(0).jjtAccept(this, data);
+ if (o == null) {
+ return Boolean.TRUE;
+ }
+ if (o instanceof String && "".equals(o)) {
+ return Boolean.TRUE;
+ }
+ if (o.getClass().isArray() && ((Object[]) o).length == 0) {
+ return Boolean.TRUE;
+ }
+ if (o instanceof Collection<?> && ((Collection<?>) o).isEmpty()) {
+ return Boolean.TRUE;
+ }
+ // Map isn't a collection
+ if (o instanceof Map<?, ?> && ((Map<?, ?>) o).isEmpty()) {
+ return Boolean.TRUE;
+ }
+ return Boolean.FALSE;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTEQNode node, Object data) {
+ Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ return arithmetic.equals(left, right) ? Boolean.TRUE : Boolean.FALSE;
+ } catch (RuntimeException xrt) {
+ throw new JexlException(node, "== error", xrt);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTFalseNode node, Object data) {
+ return Boolean.FALSE;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTFloatLiteral node, Object data) {
+ Float value = (Float) node.jjtGetValue();
+ if (value == null) {
+ value = Float.valueOf(node.image);
+ node.jjtSetValue(value);
+ }
+ return value;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTForeachStatement node, Object data) {
+ Object result = null;
+ /* first objectNode is the loop variable */
+ ASTReference loopReference = (ASTReference) node.jjtGetChild(0);
+ ASTIdentifier loopVariable = (ASTIdentifier) loopReference.jjtGetChild(0);
+ /* second objectNode is the variable to iterate */
+ Object iterableValue = node.jjtGetChild(1).jjtAccept(this, data);
+ // make sure there is a value to iterate on and a statement to execute
+ if (iterableValue != null && node.jjtGetNumChildren() >= 3) {
+ /* third objectNode is the statement to execute */
+ JexlNode statement = node.jjtGetChild(2);
+ // get an iterator for the collection/array etc via the
+ // introspector.
+ Iterator<?> itemsIterator = getUberspect().getIterator(iterableValue, node);
+ if (itemsIterator != null) {
+ while (itemsIterator.hasNext()) {
+ // set loopVariable to value of iterator
+ Object value = itemsIterator.next();
+ context.set(loopVariable.image, value);
+ // execute statement
+ result = statement.jjtAccept(this, data);
+ }
+ }
+ }
+ return result;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTGENode node, Object data) {
+ Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ return arithmetic.greaterThanOrEqual(left, right) ? Boolean.TRUE : Boolean.FALSE;
+ } catch (RuntimeException xrt) {
+ throw new JexlException(node, ">= error", xrt);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTGTNode node, Object data) {
+ Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ return arithmetic.greaterThan(left, right) ? Boolean.TRUE : Boolean.FALSE;
+ } catch (RuntimeException xrt) {
+ throw new JexlException(node, "> error", xrt);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTERNode node, Object data) {
+ Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ return arithmetic.matches(left, right) ? Boolean.TRUE : Boolean.FALSE;
+ } catch (RuntimeException xrt) {
+ throw new JexlException(node, "=~ error", xrt);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTIdentifier node, Object data) {
+ String name = node.image;
+ if (data == null) {
+ if (registers != null) {
+ if (registers[0].equals(name)) {
+ return registers[1];
+ }
+ if (registers[2].equals(name)) {
+ return registers[3];
+ }
+ }
+ Object value = context.get(name);
+ if (value == null
+ && !(node.jjtGetParent() instanceof ASTReference)
+ && !context.has(name)) {
+ JexlException xjexl = new JexlException(node, "undefined variable " + name);
+ return unknownVariable(xjexl);
+ }
+ return value;
+ } else {
+ return getAttribute(data, name, node);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTIfStatement node, Object data) {
+ int n = 0;
+ try {
+ Object result = null;
+ /* first objectNode is the expression */
+ Object expression = node.jjtGetChild(0).jjtAccept(this, data);
+ if (arithmetic.toBoolean(expression)) {
+ // first objectNode is true statement
+ n = 1;
+ result = node.jjtGetChild(1).jjtAccept(this, data);
+ } else {
+ // if there is a false, execute it. false statement is the second
+ // objectNode
+ if (node.jjtGetNumChildren() == 3) {
+ n = 2;
+ result = node.jjtGetChild(2).jjtAccept(this, data);
+ }
+ }
+ return result;
+ } catch (JexlException error) {
+ throw error;
+ } catch (RuntimeException xrt) {
+ throw new JexlException(node.jjtGetChild(n), "if error", xrt);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTIntegerLiteral node, Object data) {
+ if (data != null) {
+ Integer value = Integer.valueOf(node.image);
+ return getAttribute(data, value, node);
+ }
+ Integer value = (Integer) node.jjtGetValue();
+ if (value == null) {
+ value = Integer.valueOf(node.image);
+ node.jjtSetValue(value);
+ }
+ return value;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTJexlScript node, Object data) {
+ int numChildren = node.jjtGetNumChildren();
+ Object result = null;
+ for (int i = 0; i < numChildren; i++) {
+ JexlNode child = node.jjtGetChild(i);
+ result = child.jjtAccept(this, data);
+ }
+ return result;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTLENode node, Object data) {
+ Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ return arithmetic.lessThanOrEqual(left, right) ? Boolean.TRUE : Boolean.FALSE;
+ } catch (RuntimeException xrt) {
+ throw new JexlException(node, "<= error", xrt);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTLTNode node, Object data) {
+ Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ return arithmetic.lessThan(left, right) ? Boolean.TRUE : Boolean.FALSE;
+ } catch (RuntimeException xrt) {
+ throw new JexlException(node, "< error", xrt);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTMapEntry node, Object data) {
+ Object key = node.jjtGetChild(0).jjtAccept(this, data);
+ Object value = node.jjtGetChild(1).jjtAccept(this, data);
+ return new Object[]{key, value};
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTMapLiteral node, Object data) {
+ int childCount = node.jjtGetNumChildren();
+ Map<Object, Object> map = new HashMap<Object, Object>();
+
+ for (int i = 0; i < childCount; i++) {
+ Object[] entry = (Object[]) (node.jjtGetChild(i)).jjtAccept(this, data);
+ map.put(entry[0], entry[1]);
+ }
+
+ return map;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTMethodNode node, Object data) {
+ // the object to invoke the method on should be in the data argument
+ if (data == null) {
+ // if the first child of the (ASTReference) parent,
+ // it is considered as calling a 'top level' function
+ if (node.jjtGetParent().jjtGetChild(0) == node) {
+ data = resolveNamespace(null, node);
+ if (data == null) {
+ throw new JexlException(node, "no default function namespace");
+ }
+ } else {
+ throw new JexlException(node, "attempting to call method on null");
+ }
+ }
+ // objectNode 0 is the identifier (method name), the others are parameters.
+ String methodName = ((ASTIdentifier) node.jjtGetChild(0)).image;
+
+ // get our arguments
+ int argc = node.jjtGetNumChildren() - 1;
+ Object[] argv = new Object[argc];
+ for (int i = 0; i < argc; i++) {
+ argv[i] = node.jjtGetChild(i + 1).jjtAccept(this, null);
+ }
+
+ JexlException xjexl = null;
+ try {
+ // attempt to reuse last executor cached in volatile JexlNode.value
+ if (cache) {
+ Object cached = node.jjtGetValue();
+ if (cached instanceof JexlMethod) {
+ JexlMethod me = (JexlMethod) cached;
+ Object eval = me.tryInvoke(methodName, data, argv);
+ if (!me.tryFailed(eval)) {
+ return eval;
+ }
+ }
+ }
+ JexlMethod vm = uberspect.getMethod(data, methodName, argv, node);
+ // DG: If we can't find an exact match, narrow the parameters and try again!
+ if (vm == null) {
+ if (arithmetic.narrowArguments(argv)) {
+ vm = uberspect.getMethod(data, methodName, argv, node);
+ }
+ if (vm == null) {
+ xjexl = new JexlException(node, "unknown or ambiguous method", null);
+ }
+ }
+ if (xjexl == null) {
+ Object eval = vm.invoke(data, argv); // vm cannot be null if xjexl is null
+ // cache executor in volatile JexlNode.value
+ if (cache && vm.isCacheable()) {
+ node.jjtSetValue(vm);
+ }
+ return eval;
+ }
+ } catch (InvocationTargetException e) {
+ xjexl = new JexlException(node, "method invocation error", e.getCause());
+ } catch (Exception e) {
+ xjexl = new JexlException(node, "method error", e);
+ }
+ return invocationFailed(xjexl);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTConstructorNode node, Object data) {
+ // first child is class or class name
+ Object cobject = node.jjtGetChild(0).jjtAccept(this, data);
+ // get the ctor args
+ int argc = node.jjtGetNumChildren() - 1;
+ Object[] argv = new Object[argc];
+ for (int i = 0; i < argc; i++) {
+ argv[i] = node.jjtGetChild(i + 1).jjtAccept(this, null);
+ }
+
+ JexlException xjexl = null;
+ try {
+ Constructor<?> ctor = uberspect.getConstructor(cobject, argv, node);
+ // DG: If we can't find an exact match, narrow the parameters and
+ // try again!
+ if (ctor == null) {
+ if (arithmetic.narrowArguments(argv)) {
+ ctor = uberspect.getConstructor(cobject, argv, node);
+ }
+ if (ctor == null) {
+ xjexl = new JexlException(node, "unknown constructor", null);
+ }
+ }
+ if (xjexl == null) {
+ return ctor.newInstance(argv);
+ }
+ } catch (InvocationTargetException e) {
+ xjexl = new JexlException(node, "constructor invocation error", e.getCause());
+ } catch (Exception e) {
+ xjexl = new JexlException(node, "constructor error", e);
+ }
+ return invocationFailed(xjexl);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTFunctionNode node, Object data) {
+ // objectNode 0 is the prefix
+ String prefix = ((ASTIdentifier) node.jjtGetChild(0)).image;
+ Object namespace = resolveNamespace(prefix, node);
+ // objectNode 1 is the identifier , the others are parameters.
+ String function = ((ASTIdentifier) node.jjtGetChild(1)).image;
+
+ // get our args
+ int argc = node.jjtGetNumChildren() - 2;
+ Object[] argv = new Object[argc];
+ for (int i = 0; i < argc; i++) {
+ argv[i] = node.jjtGetChild(i + 2).jjtAccept(this, null);
+ }
+
+ JexlException xjexl = null;
+ try {
+ // attempt to reuse last executor cached in volatile JexlNode.value
+ if (cache) {
+ Object cached = node.jjtGetValue();
+ if (cached instanceof JexlMethod) {
+ JexlMethod me = (JexlMethod) cached;
+ Object eval = me.tryInvoke(function, namespace, argv);
+ if (!me.tryFailed(eval)) {
+ return eval;
+ }
+ }
+ }
+ JexlMethod vm = uberspect.getMethod(namespace, function, argv, node);
+ // DG: If we can't find an exact match, narrow the parameters and
+ // try again!
+ if (vm == null) {
+ // replace all numbers with the smallest type that will fit
+ if (arithmetic.narrowArguments(argv)) {
+ vm = uberspect.getMethod(namespace, function, argv, node);
+ }
+ if (vm == null) {
+ xjexl = new JexlException(node, "unknown function", null);
+ }
+ }
+ if (xjexl == null) {
+ Object eval = vm.invoke(namespace, argv); // vm cannot be null if xjexl is null
+ // cache executor in volatile JexlNode.value
+ if (cache && vm.isCacheable()) {
+ node.jjtSetValue(vm);
+ }
+ return eval;
+ }
+ } catch (InvocationTargetException e) {
+ xjexl = new JexlException(node, "function invocation error", e.getCause());
+ } catch (Exception e) {
+ xjexl = new JexlException(node, "function error", e);
+ }
+ return invocationFailed(xjexl);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTModNode node, Object data) {
+ Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ return arithmetic.mod(left, right);
+ } catch (RuntimeException xrt) {
+ if (!strict && xrt instanceof ArithmeticException) {
+ return new Double(0.0);
+ }
+ JexlNode xnode = findNullOperand(xrt, node, left, right);
+ throw new JexlException(xnode, "% error", xrt);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTMulNode node, Object data) {
+ Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ return arithmetic.multiply(left, right);
+ } catch (RuntimeException xrt) {
+ JexlNode xnode = findNullOperand(xrt, node, left, right);
+ throw new JexlException(xnode, "* error", xrt);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTNENode node, Object data) {
+ Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ return arithmetic.equals(left, right) ? Boolean.FALSE : Boolean.TRUE;
+ } catch (RuntimeException xrt) {
+ JexlNode xnode = findNullOperand(xrt, node, left, right);
+ throw new JexlException(xnode, "!= error", xrt);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTNRNode node, Object data) {
+ Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ return arithmetic.matches(left, right) ? Boolean.FALSE : Boolean.TRUE;
+ } catch (RuntimeException xrt) {
+ throw new JexlException(node, "!~ error", xrt);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTNotNode node, Object data) {
+ Object val = node.jjtGetChild(0).jjtAccept(this, data);
+ return arithmetic.toBoolean(val) ? Boolean.FALSE : Boolean.TRUE;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTNullLiteral node, Object data) {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTOrNode node, Object data) {
+ Object left = node.jjtGetChild(0).jjtAccept(this, data);
+ try {
+ boolean leftValue = arithmetic.toBoolean(left);
+ if (leftValue) {
+ return Boolean.TRUE;
+ }
+ } catch (RuntimeException xrt) {
+ throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt);
+ }
+ Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ try {
+ boolean rightValue = arithmetic.toBoolean(right);
+ if (rightValue) {
+ return Boolean.TRUE;
+ }
+ } catch (RuntimeException xrt) {
+ throw new JexlException(node.jjtGetChild(1), "boolean coercion error", xrt);
+ }
+ return Boolean.FALSE;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTReference node, Object data) {
+ // could be array access, identifier or map literal
+ // followed by zero or more ("." and array access, method, size,
+ // identifier or integer literal)
+
+ int numChildren = node.jjtGetNumChildren();
+
+ // pass first piece of data in and loop through children
+ Object result = null;
+ StringBuilder variableName = null;
+ boolean isVariable = true;
+ int v = 0;
+ for (int c = 0; c < numChildren; c++) {
+ JexlNode theNode = node.jjtGetChild(c);
+ isVariable &= (theNode instanceof ASTIdentifier);
+ result = theNode.jjtAccept(this, result);
+ // if we get null back a result, check for an ant variable
+ if (result == null && isVariable) {
+ if (v == 0) {
+ variableName = new StringBuilder(node.jjtGetChild(0).image);
+ v = 1;
+ }
+ for(; v <= c; ++v) {
+ variableName.append('.');
+ variableName.append(node.jjtGetChild(v).image);
+ }
+ result = context.get(variableName.toString());
+ }
+ }
+ if (result == null) {
+ if (isVariable
+ && !(node.jjtGetParent() instanceof ASTTernaryNode)
+ && !context.has(variableName.toString())) {
+ JexlException xjexl = new JexlException(node, "undefined variable " + variableName.toString());
+ return unknownVariable(xjexl);
+ }
+ }
+ return result;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTSizeFunction node, Object data) {
+ Object val = node.jjtGetChild(0).jjtAccept(this, data);
+
+ if (val == null) {
+ throw new JexlException(node, "size() : argument is null", null);
+ }
+
+ return Integer.valueOf(sizeOf(node, val));
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTSizeMethod node, Object data) {
+ return Integer.valueOf(sizeOf(node, data));
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTStringLiteral node, Object data) {
+ return node.image;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTTernaryNode node, Object data) {
+ Object condition = node.jjtGetChild(0).jjtAccept(this, data);
+ if (node.jjtGetNumChildren() == 3) {
+ if (condition != null && arithmetic.toBoolean(condition)) {
+ return node.jjtGetChild(1).jjtAccept(this, data);
+ } else {
+ return node.jjtGetChild(2).jjtAccept(this, data);
+ }
+ }
+ if (condition != null && !Boolean.FALSE.equals(condition)) {
+ return condition;
+ } else {
+ return node.jjtGetChild(1).jjtAccept(this, data);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTTrueNode node, Object data) {
+ return Boolean.TRUE;
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTUnaryMinusNode node, Object data) {
+ JexlNode valNode = node.jjtGetChild(0);
+ Object val = valNode.jjtAccept(this, data);
+ if (val instanceof Byte) {
+ byte valueAsByte = ((Byte) val).byteValue();
+ return Byte.valueOf((byte) -valueAsByte);
+ } else if (val instanceof Short) {
+ short valueAsShort = ((Short) val).shortValue();
+ return Short.valueOf((short) -valueAsShort);
+ } else if (val instanceof Integer) {
+ int valueAsInt = ((Integer) val).intValue();
+ return Integer.valueOf(-valueAsInt);
+ } else if (val instanceof Long) {
+ long valueAsLong = ((Long) val).longValue();
+ return Long.valueOf(-valueAsLong);
+ } else if (val instanceof Float) {
+ float valueAsFloat = ((Float) val).floatValue();
+ return new Float(-valueAsFloat);
+ } else if (val instanceof Double) {
+ double valueAsDouble = ((Double) val).doubleValue();
+ return new Double(-valueAsDouble);
+ } else if (val instanceof BigDecimal) {
+ BigDecimal valueAsBigD = (BigDecimal) val;
+ return valueAsBigD.negate();
+ } else if (val instanceof BigInteger) {
+ BigInteger valueAsBigI = (BigInteger) val;
+ return valueAsBigI.negate();
+ }
+ throw new JexlException(valNode, "not a number");
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTWhileStatement node, Object data) {
+ Object result = null;
+ /* first objectNode is the expression */
+ Node expressionNode = node.jjtGetChild(0);
+ while (arithmetic.toBoolean(expressionNode.jjtAccept(this, data))) {
+ // execute statement
+ result = node.jjtGetChild(1).jjtAccept(this, data);
+ }
+
+ return result;
+ }
+
+ /**
+ * Calculate the <code>size</code> of various types: Collection, Array,
+ * Map, String, and anything that has a int size() method.
+ * @param node the node that gave the value to size
+ * @param val the object to get the size of.
+ * @return the size of val
+ */
+ private int sizeOf(JexlNode node, Object val) {
+ if (val instanceof Collection<?>) {
+ return ((Collection<?>) val).size();
+ } else if (val.getClass().isArray()) {
+ return Array.getLength(val);
+ } else if (val instanceof Map<?, ?>) {
+ return ((Map<?, ?>) val).size();
+ } else if (val instanceof String) {
+ return ((String) val).length();
+ } else {
+ // check if there is a size method on the object that returns an
+ // integer and if so, just use it
+ Object[] params = new Object[0];
+ JexlMethod vm = uberspect.getMethod(val, "size", EMPTY_PARAMS, node);
+ if (vm != null && vm.getReturnType() == Integer.TYPE) {
+ Integer result;
+ try {
+ result = (Integer) vm.invoke(val, params);
+ } catch (Exception e) {
+ throw new JexlException(node, "size() : error executing", e);
+ }
+ return result.intValue();
+ }
+ throw new JexlException(node, "size() : unsupported type : " + val.getClass(), null);
+ }
+ }
+
+ /**
+ * Gets an attribute of an object.
+ *
+ * @param object to retrieve value from
+ * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
+ * key for a map
+ * @return the attribute value
+ */
+ public Object getAttribute(Object object, Object attribute) {
+ return getAttribute(object, attribute, null);
+ }
+
+ /**
+ * Gets an attribute of an object.
+ *
+ * @param object to retrieve value from
+ * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
+ * key for a map
+ * @param node the node that evaluated as the object
+ * @return the attribute value
+ */
+ protected Object getAttribute(Object object, Object attribute, JexlNode node) {
+ if (object == null) {
+ throw new JexlException(node, "object is null");
+ }
+ // attempt to reuse last executor cached in volatile JexlNode.value
+ if (node != null && cache) {
+ Object cached = node.jjtGetValue();
+ if (cached instanceof JexlPropertyGet) {
+ JexlPropertyGet vg = (JexlPropertyGet) cached;
+ Object value = vg.tryInvoke(object, attribute);
+ if (!vg.tryFailed(value)) {
+ return value;
+ }
+ }
+ }
+ JexlPropertyGet vg = uberspect.getPropertyGet(object, attribute, node);
+ if (vg != null) {
+ try {
+ Object value = vg.invoke(object);
+ // cache executor in volatile JexlNode.value
+ if (node != null && cache && vg.isCacheable()) {
+ node.jjtSetValue(vg);
+ }
+ return value;
+ } catch (Exception xany) {
+ if (node == null) {
+ throw new RuntimeException(xany);
+ } else {
+ JexlException xjexl = new JexlException(node, "get object property error", xany);
+ if (strict) {
+ throw xjexl;
+ }
+ if (!silent) {
+ logger.warn(xjexl.getMessage());
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Sets an attribute of an object.
+ *
+ * @param object to set the value to
+ * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
+ * key for a map
+ * @param value the value to assign to the object's attribute
+ */
+ public void setAttribute(Object object, Object attribute, Object value) {
+ setAttribute(object, attribute, value, null);
+ }
+
+ /**
+ * Sets an attribute of an object.
+ *
+ * @param object to set the value to
+ * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
+ * key for a map
+ * @param value the value to assign to the object's attribute
+ * @param node the node that evaluated as the object
+ */
+ protected void setAttribute(Object object, Object attribute, Object value, JexlNode node) {
+ // attempt to reuse last executor cached in volatile JexlNode.value
+ if (node != null && cache) {
+ Object cached = node.jjtGetValue();
+ if (cached instanceof JexlPropertySet) {
+ JexlPropertySet setter = (JexlPropertySet) cached;
+ Object eval = setter.tryInvoke(object, attribute, value);
+ if (!setter.tryFailed(eval)) {
+ return;
+ }
+ }
+ }
+ JexlException xjexl = null;
+ JexlPropertySet vs = uberspect.getPropertySet(object, attribute, value, node);
+ if (vs != null) {
+ try {
+ // cache executor in volatile JexlNode.value
+ vs.invoke(object, value);
+ if (node != null && cache && vs.isCacheable()) {
+ node.jjtSetValue(vs);
+ }
+ return;
+ } catch (RuntimeException xrt) {
+ if (node == null) {
+ throw xrt;
+ }
+ xjexl = new JexlException(node, "set object property error", xrt);
+ } catch (Exception xany) {
+ if (node == null) {
+ throw new RuntimeException(xany);
+ }
+ xjexl = new JexlException(node, "set object property error", xany);
+ }
+ }
+ if (xjexl == null) {
+ String error = "unable to set object property"
+ + ", class: " + object.getClass().getName()
+ + ", property: " + attribute;
+ if (node == null) {
+ throw new UnsupportedOperationException(error);
+ }
+ xjexl = new JexlException(node, error, null);
+ }
+ if (strict) {
+ throw xjexl;
+ }
+ if (!silent) {
+ logger.warn(xjexl.getMessage());
+ }
+ }
+
+ /**
+ * Unused, satisfy ParserVisitor interface.
+ * @param node a node
+ * @param data the data
+ * @return does not return
+ */
+ public Object visit(SimpleNode node, Object data) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ /**
+ * Unused, should throw in Parser.
+ * @param node a node
+ * @param data the data
+ * @return does not return
+ */
+ public Object visit(ASTAmbiguous node, Object data) {
+ throw new UnsupportedOperationException("unexpected type of node");
+ }
+}
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/JexlArithmetic.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/JexlArithmetic.java
new file mode 100644
index 0000000..69e926f
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/JexlArithmetic.java
@@ -0,0 +1,848 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+/**
+ * Perform arithmetic.
+ * <p>
+ * All arithmetic operators (+, - , *, /, %) follow the same rules regarding their arguments.
+ * <ol>
+ * <li>If both are null, result is 0</li>
+ * <li>If either is a floating point number, coerce both to Double and perform operation</li>
+ * <li>If both are BigInteger, treat as BigInteger and perform operation</li>
+ * <li>If either is a BigDecimal, coerce both to BigDecimal and and perform operation</li>
+ * <li>Else treat as BigInteger, perform operation and attempt to narrow result:
+ * <ol>
+ * <li>if both arguments can be narrowed to Integer, narrow result to Integer</li>
+ * <li>if both arguments can be narrowed to Long, narrow result to Long</li>
+ * <li>Else return result as BigInteger</li>
+ * </ol>
+ * </li>
+ * </ol>
+ * </p>
+ * @since 2.0
+ */
+public class JexlArithmetic {
+ /** Double.MAX_VALUE as BigDecimal. */
+ protected static final BigDecimal BIGD_DOUBLE_MAX_VALUE = BigDecimal.valueOf(Double.MAX_VALUE);
+ /** Double.MIN_VALUE as BigDecimal. */
+ protected static final BigDecimal BIGD_DOUBLE_MIN_VALUE = BigDecimal.valueOf(Double.MIN_VALUE);
+ /** Long.MAX_VALUE as BigInteger. */
+ protected static final BigInteger BIGI_LONG_MAX_VALUE = BigInteger.valueOf(Long.MAX_VALUE);
+ /** Long.MIN_VALUE as BigInteger. */
+ protected static final BigInteger BIGI_LONG_MIN_VALUE = BigInteger.valueOf(Long.MIN_VALUE);
+ /** Whether this JexlArithmetic instance behaves in strict or lenient mode. */
+ private boolean strict;
+
+ /**
+ * Creates a JexlArithmetic.
+ * @param lenient whether this arithmetic is lenient or strict
+ */
+ public JexlArithmetic(boolean lenient) {
+ this.strict = !lenient;
+ }
+
+ /**
+ * Sets whether this JexlArithmetic instance triggers errors during evaluation when
+ * null is used as an operand.
+ * <p>This method is <em>not</em> thread safe; it may be called as an optional step by the JexlEngine
+ * in its initialization code before expression creation & evaluation.</p>
+ * @see JexlEngine#setSilent
+ * @see JexlEngine#setDebug
+ * @param lenient true means no JexlException will occur, false allows them
+ */
+ void setLenient(boolean lenient) {
+ this.strict = !lenient;
+ }
+
+ /**
+ * Checks whether this JexlArithmetic instance triggers errors during evaluation
+ * when null is used as an operand.
+ * @return true if lenient, false if strict
+ */
+ public boolean isLenient() {
+ return !this.strict;
+ }
+
+ /**
+ * The result of +,/,-,*,% when both operands are null.
+ * @return null if strict, else Long(0)
+ */
+ protected Object controlNullNullOperands() {
+ return strict? null : Integer.valueOf(0);
+ }
+
+ /**
+ * Throw a NPE if arithmetic is strict.
+ */
+ protected void controlNullOperand() {
+ if (strict) {
+ throw new NullPointerException(JexlException.NULL_OPERAND);
+ }
+ }
+
+ /**
+ * Test if either left or right are either a Float or Double.
+ * @param left one object to test
+ * @param right the other
+ * @return the result of the test.
+ */
+ protected boolean isFloatingPointType(Object left, Object right) {
+ return left instanceof Float || left instanceof Double || right instanceof Float || right instanceof Double;
+ }
+
+ /**
+ * Test if the passed value is a floating point number, i.e. a float, double
+ * or string with ( "." | "E" | "e").
+ *
+ * @param val the object to be tested
+ * @return true if it is, false otherwise.
+ */
+ protected boolean isFloatingPointNumber(Object val) {
+ if (val instanceof Float || val instanceof Double) {
+ return true;
+ }
+ if (val instanceof String) {
+ String string = (String) val;
+ return string.indexOf('.') != -1 || string.indexOf('e') != -1 || string.indexOf('E') != -1;
+ }
+ return false;
+ }
+
+ /**
+ * Is Object a floating point number.
+ *
+ * @param o Object to be analyzed.
+ * @return true if it is a Float or a Double.
+ */
+ protected boolean isFloatingPoint(final Object o) {
+ return o instanceof Float || o instanceof Double;
+ }
+
+ /**
+ * Is Object a whole number.
+ *
+ * @param o Object to be analyzed.
+ * @return true if Integer, Long, Byte, Short or Character.
+ */
+ protected boolean isNumberable(final Object o) {
+ return o instanceof Integer
+ || o instanceof Long
+ || o instanceof Byte
+ || o instanceof Short
+ || o instanceof Character;
+ }
+
+ /**
+ * Given a BigInteger, narrow it to an Integer or Long if it fits and the arguments
+ * class allow it.
+ * <p>
+ * The rules are:
+ * if either arguments is a BigInteger, no narrowing will occur
+ * if either arguments is a Long, no narrowing to Integer will occur
+ * </p>
+ * @param lhs the left hand side operand that lead to the bigi result
+ * @param rhs the right hand side operand that lead to the bigi result
+ * @param bigi the BigInteger to narrow
+ * @return an Integer or Long if narrowing is possible, the original BigInteger otherwise
+ */
+ protected Number narrowBigInteger(Object lhs, Object rhs, BigInteger bigi) {
+ //coerce to long if possible
+ if (!(lhs instanceof BigInteger || rhs instanceof BigInteger)
+ && bigi.compareTo(BIGI_LONG_MAX_VALUE) <= 0
+ && bigi.compareTo(BIGI_LONG_MIN_VALUE) >= 0) {
+ // coerce to int if possible
+ long l = bigi.longValue();
+ // coerce to int when possible (int being so often used in method parms)
+ if (!(lhs instanceof Long || rhs instanceof Long)
+ && l <= Integer.MAX_VALUE
+ && l >= Integer.MIN_VALUE) {
+ return Integer.valueOf((int) l);
+ }
+ return Long.valueOf(l);
+ }
+ return bigi;
+ }
+
+ /**
+ * Given an array of objects, attempt to type it more strictly.
+ * <ul>
+ * <li>If all objects are of the same type, the array returned will be an array of that same type</li>
+ * <li>If all objects are Numbers, the array returned will be an array of Numbers</li>
+ * <li>If all objects are convertible to a primitive type, the array returned will be an array
+ * of the primitive type</li>
+ * </ul>
+ * @param untyped an untyped array
+ * @return the original array if the attempt to strictly type the array fails, a typed array otherwise
+ */
+ protected Object narrowArrayType(Object[] untyped) {
+ final int size = untyped.length;
+ Class<?> commonClass = null;
+ if (size > 0) {
+ // base common class on first entry
+ commonClass = untyped[0].getClass();
+ final boolean isNumber = Number.class.isAssignableFrom(commonClass);
+ // for all children after first...
+ for (int i = 1; i < size; i++) {
+ Class<?> eclass = untyped[i].getClass();
+ // detect same type for all elements in array
+ if (!Object.class.equals(commonClass) && !commonClass.equals(eclass)) {
+ // if both are numbers...
+ if (isNumber && Number.class.isAssignableFrom(eclass)) {
+ commonClass = Number.class;
+ } else {
+ commonClass = Object.class;
+ }
+ }
+ }
+ // convert array to the common class if not Object.class
+ if (!Object.class.equals(commonClass)) {
+ // if the commonClass has an equivalent primitive type, get it
+ if (isNumber) {
+ try {
+ Field TYPE = commonClass.getField("TYPE");
+ commonClass = (Class<?>) TYPE.get(null);
+ } catch (Exception xany) {
+ // ignore
+ }
+ }
+ // allocate and fill up the typed array
+ Object typed = Array.newInstance(commonClass, size);
+ for(int i = 0; i < size; ++i) {
+ Array.set(typed, i, untyped[i]);
+ }
+ return typed;
+ }
+ }
+ return untyped;
+ }
+
+ /**
+ * Replace all numbers in an arguments array with the smallest type that will fit.
+ * @param args the argument array
+ * @return true if some arguments were narrowed and args array is modified,
+ * false if no narrowing occured and args array has not been modified
+ */
+ protected boolean narrowArguments(Object[] args) {
+ boolean narrowed = false;
+ for (int a = 0; a < args.length; ++a) {
+ Object arg = args[a];
+ if (arg instanceof Number) {
+ Object narg = narrow((Number) arg);
+ if (narg != arg) {
+ narrowed = true;
+ }
+ args[a] = narg;
+ }
+ }
+ return narrowed;
+ }
+
+ /**
+ * Add two values together.
+ * <p>
+ * If any numeric add fails on coercion to the appropriate type,
+ * treat as Strings and do concatenation.
+ * </p>
+ * @param left first value
+ * @param right second value
+ * @return left + right.
+ */
+ public Object add(Object left, Object right) {
+ if (left == null && right == null) {
+ return controlNullNullOperands();
+ }
+
+ try {
+ // if either are floating point (double or float) use double
+ if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
+ double l = toDouble(left);
+ double r = toDouble(right);
+ return new Double(l + r);
+ }
+
+ // if both are bigintegers use that type
+ if (left instanceof BigInteger && right instanceof BigInteger) {
+ BigInteger l = toBigInteger(left);
+ BigInteger r = toBigInteger(right);
+ return l.add(r);
+ }
+
+ // if either are bigdecimal use that type
+ if (left instanceof BigDecimal || right instanceof BigDecimal) {
+ BigDecimal l = toBigDecimal(left);
+ BigDecimal r = toBigDecimal(right);
+ return l.add(r);
+ }
+
+ // otherwise treat as integers
+ BigInteger l = toBigInteger(left);
+ BigInteger r = toBigInteger(right);
+ BigInteger result = l.add(r);
+ return narrowBigInteger(left, right, result);
+ } catch (java.lang.NumberFormatException nfe) {
+ // Well, use strings!
+ return toString(left).concat(toString(right));
+ }
+ }
+
+ /**
+ * Divide the left value by the right.
+ * @param left first value
+ * @param right second value
+ * @return left / right
+ * @throws ArithmeticException if right == 0
+ */
+ public Object divide(Object left, Object right) {
+ if (left == null && right == null) {
+ return controlNullNullOperands();
+ }
+
+ // if either are floating point (double or float) use double
+ if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
+ double l = toDouble(left);
+ double r = toDouble(right);
+ if (r == 0.0) {
+ throw new ArithmeticException("/");
+ }
+ return new Double(l / r);
+ }
+
+ // if both are bigintegers use that type
+ if (left instanceof BigInteger && right instanceof BigInteger) {
+ BigInteger l = toBigInteger(left);
+ BigInteger r = toBigInteger(right);
+ return l.divide(r);
+ }
+
+ // if either are bigdecimal use that type
+ if (left instanceof BigDecimal || right instanceof BigDecimal) {
+ BigDecimal l = toBigDecimal(left);
+ BigDecimal r = toBigDecimal(right);
+ BigDecimal d = l.divide(r);
+ return d;
+ }
+
+ // otherwise treat as integers
+ BigInteger l = toBigInteger(left);
+ BigInteger r = toBigInteger(right);
+ BigInteger result = l.divide(r);
+ return narrowBigInteger(left, right, result);
+ }
+
+ /**
+ * left value mod right.
+ * @param left first value
+ * @param right second value
+ * @return left mod right
+ * @throws ArithmeticException if right == 0.0
+ */
+ public Object mod(Object left, Object right) {
+ if (left == null && right == null) {
+ return controlNullNullOperands();
+ }
+
+ // if either are floating point (double or float) use double
+ if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
+ double l = toDouble(left);
+ double r = toDouble(right);
+ if (r == 0.0) {
+ throw new ArithmeticException("%");
+ }
+ return new Double(l % r);
+ }
+
+ // if both are bigintegers use that type
+ if (left instanceof BigInteger && right instanceof BigInteger) {
+ BigInteger l = toBigInteger(left);
+ BigInteger r = toBigInteger(right);
+ return l.mod(r);
+ }
+
+ // if either are bigdecimal use that type
+ if (left instanceof BigDecimal || right instanceof BigDecimal) {
+ BigDecimal l = toBigDecimal(left);
+ BigDecimal r = toBigDecimal(right);
+ BigDecimal remainder = l.remainder(r);
+ return remainder;
+ }
+
+ // otherwise treat as integers
+ BigInteger l = toBigInteger(left);
+ BigInteger r = toBigInteger(right);
+ BigInteger result = l.mod(r);
+ return narrowBigInteger(left, right, result);
+ }
+
+ /**
+ * Multiply the left value by the right.
+ * @param left first value
+ * @param right second value
+ * @return left * right.
+ */
+ public Object multiply(Object left, Object right) {
+ if (left == null && right == null) {
+ return controlNullNullOperands();
+ }
+
+ // if either are floating point (double or float) use double
+ if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
+ double l = toDouble(left);
+ double r = toDouble(right);
+ return new Double(l * r);
+ }
+
+ // if both are bigintegers use that type
+ if (left instanceof BigInteger && right instanceof BigInteger) {
+ BigInteger l = toBigInteger(left);
+ BigInteger r = toBigInteger(right);
+ return l.multiply(r);
+ }
+
+ // if either are bigdecimal use that type
+ if (left instanceof BigDecimal || right instanceof BigDecimal) {
+ BigDecimal l = toBigDecimal(left);
+ BigDecimal r = toBigDecimal(right);
+ return l.multiply(r);
+ }
+
+ // otherwise treat as integers
+ BigInteger l = toBigInteger(left);
+ BigInteger r = toBigInteger(right);
+ BigInteger result = l.multiply(r);
+ return narrowBigInteger(left, right, result);
+ }
+
+ /**
+ * Subtract the right value from the left.
+ * @param left first value
+ * @param right second value
+ * @return left - right.
+ */
+ public Object subtract(Object left, Object right) {
+ if (left == null && right == null) {
+ return controlNullNullOperands();
+ }
+
+ // if either are floating point (double or float) use double
+ if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
+ double l = toDouble(left);
+ double r = toDouble(right);
+ return new Double(l - r);
+ }
+
+ // if both are bigintegers use that type
+ if (left instanceof BigInteger && right instanceof BigInteger) {
+ BigInteger l = toBigInteger(left);
+ BigInteger r = toBigInteger(right);
+ return l.subtract(r);
+ }
+
+ // if either are bigdecimal use that type
+ if (left instanceof BigDecimal || right instanceof BigDecimal) {
+ BigDecimal l = toBigDecimal(left);
+ BigDecimal r = toBigDecimal(right);
+ return l.subtract(r);
+ }
+
+ // otherwise treat as integers
+ BigInteger l = toBigInteger(left);
+ BigInteger r = toBigInteger(right);
+ BigInteger result = l.subtract(r);
+ return narrowBigInteger(left, right, result);
+ }
+
+ /**
+ * Test if left regexp matches right.
+ *
+ * @param left first value
+ * @param right second value
+ * @return test result.
+ */
+ public boolean matches(Object left, Object right) {
+ if (left == null && right == null) {
+ //if both are null L == R
+ return true;
+ }
+ if (left == null || right == null) {
+ // we know both aren't null, therefore L != R
+ return false;
+ }
+ final String arg = left.toString();
+ if (right instanceof java.util.regex.Pattern) {
+ return ((java.util.regex.Pattern) right).matcher(arg).matches();
+ } else {
+ return arg.matches(right.toString());
+ }
+ }
+
+ /**
+ * Test if left and right are equal.
+ *
+ * @param left first value
+ * @param right second value
+ * @return test result.
+ */
+ public boolean equals(Object left, Object right) {
+ if (left == null && right == null) {
+ /*
+ * if both are null L == R
+ */
+ return true;
+ } else if (left == null || right == null) {
+ /*
+ * we know both aren't null, therefore L != R
+ */
+ return false;
+ } else if (left.getClass().equals(right.getClass())) {
+ return left.equals(right);
+ } else if (left instanceof BigDecimal || right instanceof BigDecimal) {
+ return toBigDecimal(left).compareTo(toBigDecimal(right)) == 0;
+ } else if (isFloatingPointType(left, right)) {
+ return toDouble(left) == toDouble(right);
+ } else if (left instanceof Number || right instanceof Number || left instanceof Character
+ || right instanceof Character) {
+ return toLong(left) == toLong(right);
+ } else if (left instanceof Boolean || right instanceof Boolean) {
+ return toBoolean(left) == toBoolean(right);
+ } else if (left instanceof java.lang.String || right instanceof String) {
+ return left.toString().equals(right.toString());
+ }
+
+ return left.equals(right);
+ }
+
+
+ /**
+ * Test if left < right.
+ *
+ * @param left first value
+ * @param right second value
+ * @return test result.
+ */
+ public boolean lessThan(Object left, Object right) {
+ if ((left == right) || (left == null) || (right == null)) {
+ return false;
+ } else if (isFloatingPoint(left) || isFloatingPoint(right)) {
+ double leftDouble = toDouble(left);
+ double rightDouble = toDouble(right);
+ return leftDouble < rightDouble;
+ } else if (left instanceof BigDecimal || right instanceof BigDecimal) {
+ BigDecimal l = toBigDecimal(left);
+ BigDecimal r = toBigDecimal(right);
+ return l.compareTo(r) < 0;
+ } else if (isNumberable(left) || isNumberable(right)) {
+ long leftLong = toLong(left);
+ long rightLong = toLong(right);
+ return leftLong < rightLong;
+ } else if (left instanceof String || right instanceof String) {
+ String leftString = left.toString();
+ String rightString = right.toString();
+ return leftString.compareTo(rightString) < 0;
+ } else if (left instanceof Comparable<?>) {
+ @SuppressWarnings("unchecked") // OK because of instanceof check above
+ final Comparable<Object> comparable = (Comparable<Object>) left;
+ return comparable.compareTo(right) < 0;
+ } else if (right instanceof Comparable<?>) {
+ @SuppressWarnings("unchecked") // OK because of instanceof check above
+ final Comparable<Object> comparable = (Comparable<Object>) right;
+ return comparable.compareTo(left) > 0;
+ }
+
+ throw new IllegalArgumentException("Invalid comparison : comparing cardinality for left: " + left
+ + " and right: " + right);
+
+ }
+
+ /**
+ * Test if left > right.
+ *
+ * @param left first value
+ * @param right second value
+ * @return test result.
+ */
+ public boolean greaterThan(Object left, Object right) {
+ if (left == null || right == null) {
+ return false;
+ }
+ return !equals(left, right) && !lessThan(left, right);
+ }
+
+ /**
+ * Test if left <= right.
+ *
+ * @param left first value
+ * @param right second value
+ * @return test result.
+ */
+ public boolean lessThanOrEqual(Object left, Object right) {
+ return equals(left, right) || lessThan(left, right);
+ }
+
+ /**
+ * Test if left >= right.
+ *
+ * @param left first value
+ * @param right second value
+ * @return test result.
+ */
+ public boolean greaterThanOrEqual(Object left, Object right) {
+ return equals(left, right) || greaterThan(left, right);
+ }
+
+ /**
+ * Coerce to a boolean (not a java.lang.Boolean).
+ *
+ * @param val Object to be coerced.
+ * @return The boolean coerced value, or false if none possible.
+ */
+ public boolean toBoolean(Object val) {
+ if (val == null) {
+ controlNullOperand();
+ return false;
+ } else if (val instanceof Boolean) {
+ return ((Boolean) val).booleanValue();
+ } else if (val instanceof String) {
+ return Boolean.valueOf((String) val).booleanValue();
+ }
+ // TODO: is this a reasonable default?
+ return false;
+ }
+
+ /**
+ * Coerce to a int.
+ *
+ * @param val Object to be coerced.
+ * @return The int coerced value.
+ */
+ public int toInteger(Object val) {
+ if (val == null) {
+ controlNullOperand();
+ return 0;
+ } else if (val instanceof String) {
+ if ("".equals(val)) {
+ return 0;
+ }
+ return Integer.parseInt((String) val);
+ } else if (val instanceof Character) {
+ return ((Character) val).charValue();
+ } else if (val instanceof Boolean) {
+ throw new IllegalArgumentException("Boolean->Integer coercion exception");
+ } else if (val instanceof Number) {
+ return ((Number) val).intValue();
+ }
+
+ throw new IllegalArgumentException("Integer coercion exception. Can't coerce type: "
+ + val.getClass().getName());
+ }
+
+
+ /**
+ * Coerce to a long (not a java.lang.Long).
+ *
+ * @param val Object to be coerced.
+ * @return The long coerced value.
+ */
+ public long toLong(Object val) {
+ if (val == null) {
+ controlNullOperand();
+ return 0L;
+ } else if (val instanceof String) {
+ if ("".equals(val)) {
+ return 0;
+ }
+ return Long.parseLong((String) val);
+ } else if (val instanceof Character) {
+ return ((Character) val).charValue();
+ } else if (val instanceof Boolean) {
+ throw new NumberFormatException("Boolean->Long coercion exception");
+ } else if (val instanceof Number) {
+ return ((Number) val).longValue();
+ }
+
+ throw new NumberFormatException("Long coercion exception. Can't coerce type: " + val.getClass().getName());
+ }
+
+ /**
+ * Get a BigInteger from the object passed.
+ * Null and empty string maps to zero.
+ * @param val the object to be coerced.
+ * @return a BigDecimal.
+ * @throws NullPointerException if val is null and mode is strict.
+ */
+ public BigInteger toBigInteger(Object val) {
+ if (val instanceof BigInteger) {
+ return (BigInteger) val;
+ } else if (val == null) {
+ controlNullOperand();
+ return BigInteger.valueOf(0);
+ } else if (val instanceof String) {
+ String string = (String) val;
+ if ("".equals(string.trim())) {
+ return BigInteger.ZERO;
+ }
+ return new BigInteger(string);
+ } else if (val instanceof Number) {
+ return new BigInteger(val.toString());
+ } else if (val instanceof Character) {
+ int i = ((Character) val).charValue();
+ return BigInteger.valueOf(i);
+ }
+
+ throw new IllegalArgumentException("BigInteger coercion exception. Can't coerce type: "
+ + val.getClass().getName());
+ }
+
+ /**
+ * Get a BigDecimal from the object passed.
+ * Null and empty string maps to zero.
+ * @param val the object to be coerced.
+ * @return a BigDecimal.
+ * @throws NullPointerException if val is null and mode is strict.
+ */
+ public BigDecimal toBigDecimal(Object val) {
+ if (val instanceof BigDecimal) {
+ return (BigDecimal) val;
+ } else if (val == null) {
+ controlNullOperand();
+ return BigDecimal.ZERO;
+ } else if (val instanceof String) {
+ String string = (String) val;
+ if ("".equals(string.trim())) {
+ return BigDecimal.valueOf(0);
+ }
+ return new BigDecimal(string);
+ } else if (val instanceof Number) {
+ return new BigDecimal(val.toString());
+ } else if (val instanceof Character) {
+ int i = ((Character) val).charValue();
+ return new BigDecimal(i);
+ }
+
+ throw new IllegalArgumentException("BigDecimal coercion exception. Can't coerce type: "
+ + val.getClass().getName());
+ }
+
+ /**
+ * Coerce to a double.
+ *
+ * @param val Object to be coerced.
+ * @return The double coerced value.
+ * @throws NullPointerException if val is null and mode is strict.
+ */
+ public double toDouble(Object val) {
+ if (val == null) {
+ controlNullOperand();
+ return 0;
+ } else if (val instanceof String) {
+ String string = (String) val;
+ if ("".equals(string.trim())) {
+ return 0;
+ }
+ // the spec seems to be iffy about this. Going to give it a wack anyway
+ return Double.parseDouble(string);
+ } else if (val instanceof Character) {
+ int i = ((Character) val).charValue();
+
+ return i;
+ } else if (val instanceof Double) {
+ return ((Double) val).doubleValue();
+ } else if (val instanceof Number) {
+ //The below construct is used rather than ((Number)val).doubleValue() to ensure
+ //equality between comparing new Double( 6.4 / 3 ) and the jexl expression of 6.4 / 3
+ return Double.parseDouble(String.valueOf(val));
+ } else if (val instanceof Boolean) {
+ throw new IllegalArgumentException("Boolean->Double coercion exception");
+ }
+
+ throw new IllegalArgumentException("Double coercion exception. Can't coerce type: "
+ + val.getClass().getName());
+ }
+
+
+ /**
+ * Coerce to a string.
+ *
+ * @param val Object to be coerced.
+ * @return The String coerced value.
+ * @throws NullPointerException if val is null and mode is strict.
+ */
+ public String toString(Object val) {
+ if (val == null) {
+ controlNullOperand();
+ val = "";
+ }
+ return val.toString();
+ }
+
+ /**
+ * Given a Number, return back the value using the smallest type the result
+ * will fit into. This works hand in hand with parameter 'widening' in java
+ * method calls, e.g. a call to substring(int,int) with an int and a long
+ * will fail, but a call to substring(int,int) with an int and a short will
+ * succeed.
+ *
+ * @param original the original number.
+ * @return a value of the smallest type the original number will fit into.
+ */
+ public Number narrow(Number original) {
+ if (original == null) {
+ return original;
+ }
+ Number result = original;
+ if (original instanceof BigDecimal) {
+ BigDecimal bigd = (BigDecimal) original;
+ // if it's bigger than a double it can't be narrowed
+ if (bigd.compareTo(BIGD_DOUBLE_MAX_VALUE) > 0) {
+ return original;
+ }
+ }
+ if (original instanceof Double || original instanceof Float || original instanceof BigDecimal) {
+ double value = original.doubleValue();
+ if (value <= Float.MAX_VALUE && value >= Float.MIN_VALUE) {
+ result = Float.valueOf(result.floatValue());
+ }
+ // else it fits in a double only
+ } else {
+ if (original instanceof BigInteger) {
+ BigInteger bigi = (BigInteger) original;
+ // if it's bigger than a Long it can't be narrowed
+ if (bigi.compareTo(BIGI_LONG_MAX_VALUE) > 0
+ || bigi.compareTo(BIGI_LONG_MIN_VALUE) < 0) {
+ return original;
+ }
+ }
+ long value = original.longValue();
+ if (value <= Byte.MAX_VALUE && value >= Byte.MIN_VALUE) {
+ // it will fit in a byte
+ result = Byte.valueOf((byte) value);
+ } else if (value <= Short.MAX_VALUE && value >= Short.MIN_VALUE) {
+ result = Short.valueOf((short) value);
+ } else if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {
+ result = Integer.valueOf((int) value);
+ }
+ // else it fits in a long
+ }
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/JexlContext.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/JexlContext.java
new file mode 100644
index 0000000..97ce59a
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/JexlContext.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+
+/**
+ * Manages variables which can be referenced in a JEXL expression.
+ *
+ * @since 1.0
+ * @version $Id$
+ */
+public interface JexlContext {
+ /**
+ * Gets the value of a variable.
+ * @param name the variable's name
+ * @return the value
+ */
+ Object get(String name);
+
+ /**
+ * Sets the value of a variable.
+ * @param name the variable's name
+ * @param value the variable's value
+ */
+ void set(String name, Object value);
+
+ /**
+ * Checks whether a variable is defined in this context.
+ * <p>A variable may be defined with a null value; this method checks whether the
+ * value is null or if the variable is undefined.</p>
+ * @param name the variable's name
+ * @return true if it exists, false otherwise
+ */
+ boolean has(String name);
+}
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/JexlEngine.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/JexlEngine.java
new file mode 100644
index 0000000..328358f
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/JexlEngine.java
@@ -0,0 +1,925 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.io.Reader;
+import java.net.URL;
+import java.net.URLConnection;
+import java.lang.ref.SoftReference;
+import java.lang.reflect.Constructor;
+import java.util.Map;
+import java.util.Set;
+import java.util.Collections;
+import java.util.Map.Entry;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.commons.jexl2.parser.ParseException;
+import org.apache.commons.jexl2.parser.Parser;
+import org.apache.commons.jexl2.parser.JexlNode;
+import org.apache.commons.jexl2.parser.TokenMgrError;
+import org.apache.commons.jexl2.parser.ASTJexlScript;
+
+import org.apache.commons.jexl2.introspection.Uberspect;
+import org.apache.commons.jexl2.introspection.UberspectImpl;
+import org.apache.commons.jexl2.introspection.JexlMethod;
+
+/**
+ * <p>
+ * Creates and evaluates Expression and Script objects.
+ * Determines the behavior of Expressions & Scripts during their evaluation with respect to:
+ * <ul>
+ * <li>Introspection, see {@link Uberspect}</li>
+ * <li>Arithmetic & comparison, see {@link JexlArithmetic}</li>
+ * <li>Error reporting</li>
+ * <li>Logging</li>
+ * </ul>
+ * </p>
+ * <p>The <code>setSilent</code>and<code>setLenient</code> methods allow to fine-tune an engine instance behavior
+ * according to various error control needs.
+ * </p>
+ * <ul>
+ * <li>When "silent" & "lenient" (not-strict):
+ * <p> 0 & null should be indicators of "default" values so that even in an case of error,
+ * something meaningfull can still be inferred; may be convenient for configurations.
+ * </p>
+ * </li>
+ * <li>When "silent" & "strict":
+ * <p>One should probably consider using null as an error case - ie, every object
+ * manipulated by JEXL should be valued; the ternary operator, especially the '?:' form
+ * can be used to workaround exceptional cases.
+ * Use case could be configuration with no implicit values or defaults.
+ * </p>
+ * </li>
+ * <li>When "not-silent" & "not-strict":
+ * <p>The error control grain is roughly on par with JEXL 1.0</p>
+ * </li>
+ * <li>When "not-silent" & "strict":
+ * <p>The finest error control grain is obtained; it is the closest to Java code -
+ * still augmented by "script" capabilities regarding automated conversions & type matching.
+ * </p>
+ * </li>
+ * </ul>
+ * <p>
+ * Note that methods that evaluate expressions may throw <em>unchecked</em> exceptions;
+ * The {@link JexlException} are thrown in "non-silent" mode but since these are
+ * RuntimeException, user-code <em>should</em> catch them wherever most appropriate.
+ * </p>
+ * @since 2.0
+ */
+public class JexlEngine {
+ /**
+ * An empty/static/non-mutable JexlContext used instead of null context.
+ */
+ public static final JexlContext EMPTY_CONTEXT = new JexlContext() {
+ /** {@inheritDoc} */
+ public Object get(String name) {
+ return null;
+ }
+ /** {@inheritDoc} */
+ public boolean has(String name) {
+ return false;
+ }
+ /** {@inheritDoc} */
+ public void set(String name, Object value) {
+ throw new UnsupportedOperationException("Not supported in void context.");
+ }
+ };
+
+ /**
+ * Gets the default instance of Uberspect.
+ * <p>This is lazily initialized to avoid building a default instance if there
+ * is no use for it. The main reason for not using the default Uberspect instance is to
+ * be able to use a (low level) introspector created with a given logger
+ * instead of the default one.</p>
+ * <p>Implemented as on demand holder idiom.</p>
+ */
+ private static final class UberspectHolder {
+ /** The default uberspector that handles all introspection patterns. */
+ private static final Uberspect UBERSPECT = new UberspectImpl(LogFactory.getLog(JexlEngine.class));
+ /** Non-instantiable. */
+ private UberspectHolder() {}
+ }
+
+ /**
+ * The Uberspect instance.
+ */
+ protected final Uberspect uberspect;
+ /**
+ * The JexlArithmetic instance.
+ */
+ protected final JexlArithmetic arithmetic;
+ /**
+ * The Log to which all JexlEngine messages will be logged.
+ */
+ protected final Log logger;
+ /**
+ * The singleton ExpressionFactory also holds a single instance of
+ * {@link Parser}.
+ * When parsing expressions, ExpressionFactory synchronizes on Parser.
+ */
+ protected final Parser parser = new Parser(new StringReader(";")); //$NON-NLS-1$
+ /**
+ * Whether expressions evaluated by this engine will throw exceptions (false) or
+ * return null (true). Default is false.
+ */
+ protected boolean silent = false;
+ /**
+ * Whether error messages will carry debugging information.
+ */
+ protected boolean debug = true;
+ /**
+ * The map of 'prefix:function' to object implementing the function.
+ */
+ protected Map<String, Object> functions = Collections.emptyMap();
+ /**
+ * The expression cache.
+ */
+ protected SoftCache<String, ASTJexlScript> cache = null;
+ /**
+ * The default cache load factor.
+ */
+ private static final float LOAD_FACTOR = 0.75f;
+
+ /**
+ * Creates an engine with default arguments.
+ */
+ public JexlEngine() {
+ this(null, null, null, null);
+ }
+
+ /**
+ * Creates a JEXL engine using the provided {@link Uberspect}, (@link JexlArithmetic),
+ * a function map and logger.
+ * @param anUberspect to allow different introspection behaviour
+ * @param anArithmetic to allow different arithmetic behaviour
+ * @param theFunctions an optional map of functions (@link setFunctions)
+ * @param log the logger for various messages
+ */
+ public JexlEngine(Uberspect anUberspect, JexlArithmetic anArithmetic, Map<String, Object> theFunctions, Log log) {
+ this.uberspect = anUberspect == null ? getUberspect(log) : anUberspect;
+ if (log == null) {
+ log = LogFactory.getLog(JexlEngine.class);
+ }
+ this.logger = log;
+ this.arithmetic = anArithmetic == null ? new JexlArithmetic(true) : anArithmetic;
+ if (theFunctions != null) {
+ this.functions = theFunctions;
+ }
+ }
+
+
+ /**
+ * Gets the default instance of Uberspect.
+ * <p>This is lazily initialized to avoid building a default instance if there
+ * is no use for it. The main reason for not using the default Uberspect instance is to
+ * be able to use a (low level) introspector created with a given logger
+ * instead of the default one.</p>
+ * @param logger the logger to use for the underlying Uberspect
+ * @return Uberspect the default uberspector instance.
+ */
+ public static Uberspect getUberspect(Log logger) {
+ if (logger == null || logger.equals(LogFactory.getLog(JexlEngine.class))) {
+ return UberspectHolder.UBERSPECT;
+ }
+ return new UberspectImpl(logger);
+ }
+
+ /**
+ * Gets this engine underlying uberspect.
+ * @return the uberspect
+ */
+ public Uberspect getUberspect() {
+ return uberspect;
+ }
+
+ /**
+ * Sets whether this engine reports debugging information when error occurs.
+ * <p>This method is <em>not</em> thread safe; it should be called as an optional step of the JexlEngine
+ * initialization code before expression creation & evaluation.</p>
+ * @see JexlEngine#setSilent
+ * @see JexlEngine#setLenient
+ * @param flag true implies debug is on, false implies debug is off.
+ */
+ public void setDebug(boolean flag) {
+ this.debug = flag;
+ }
+
+ /**
+ * Checks whether this engine is in debug mode.
+ * @return true if debug is on, false otherwise
+ */
+ public boolean isDebug() {
+ return this.debug;
+ }
+
+ /**
+ * Sets whether this engine throws JexlException during evaluation when an error is triggered.
+ * <p>This method is <em>not</em> thread safe; it should be called as an optional step of the JexlEngine
+ * initialization code before expression creation & evaluation.</p>
+ * @see JexlEngine#setDebug
+ * @see JexlEngine#setLenient
+ * @param flag true means no JexlException will occur, false allows them
+ */
+ public void setSilent(boolean flag) {
+ this.silent = flag;
+ }
+
+ /**
+ * Checks whether this engine throws JexlException during evaluation.
+ * @return true if silent, false (default) otherwise
+ */
+ public boolean isSilent() {
+ return this.silent;
+ }
+
+ /**
+ * Sets whether this engine triggers errors during evaluation when null is used as
+ * an operand.
+ * <p>This method is <em>not</em> thread safe; it should be called as an optional step of the JexlEngine
+ * initialization code before expression creation & evaluation.</p>
+ * @see JexlEngine#setSilent
+ * @see JexlEngine#setDebug
+ * @param flag true means no JexlException will occur, false allows them
+ */
+ public void setLenient(boolean flag) {
+ this.arithmetic.setLenient(flag);
+ }
+
+ /**
+ * Checks whether this engine triggers errors during evaluation when null is used as
+ * an operand.
+ * @return true if lenient, false if strict
+ */
+ public boolean isLenient() {
+ return this.arithmetic.isLenient();
+ }
+
+ /**
+ * Sets the class loader used to discover classes in 'new' expressions.
+ * <p>This method should be called as an optional step of the JexlEngine
+ * initialization code before expression creation & evaluation.</p>
+ * @param loader the class loader to use
+ */
+ public void setClassLoader(ClassLoader loader) {
+ uberspect.setClassLoader(loader);
+ }
+
+ /**
+ * Sets a cache of the defined size for expressions.
+ * @param size if not strictly positive, no cache is used.
+ */
+ public void setCache(int size) {
+ // since the cache is only used during parse, use same sync object
+ synchronized (parser) {
+ if (size <= 0) {
+ cache = null;
+ } else if (cache == null || cache.size() != size) {
+ cache = new SoftCache<String, ASTJexlScript>(size);
+ }
+ }
+ }
+
+ /**
+ * Sets the map of function namespaces.
+ * <p>
+ * This method is <em>not</em> thread safe; it should be called as an optional step of the JexlEngine
+ * initialization code before expression creation & evaluation.
+ * </p>
+ * <p>
+ * Each entry key is used as a prefix, each entry value used as a bean implementing
+ * methods; an expression like 'nsx:method(123)' will thus be solved by looking at
+ * a registered bean named 'nsx' that implements method 'method' in that map.
+ * If all methods are static, you may use the bean class instead of an instance as value.
+ * </p>
+ * <p>
+ * If the entry value is a class that has one contructor taking a JexlContext as argument, an instance
+ * of the namespace will be created at evaluation time. It might be a good idea to derive a JexlContext
+ * to carry the information used by the namespace to avoid variable space pollution and strongly type
+ * the constructor with this specialized JexlContext.
+ * </p>
+ * <p>
+ * The key or prefix allows to retrieve the bean that plays the role of the namespace.
+ * If the prefix is null, the namespace is the top-level namespace allowing to define
+ * top-level user defined functions ( ie: myfunc(...) )
+ * </p>
+ * @param funcs the map of functions that should not mutate after the call; if null
+ * is passed, the empty collection is used.
+ */
+ public void setFunctions(Map<String, Object> funcs) {
+ functions = funcs != null ? funcs : Collections.<String, Object>emptyMap();
+ }
+
+ /**
+ * Retrieves the map of function namespaces.
+ *
+ * @return the map passed in setFunctions or the empty map if the
+ * original was null.
+ */
+ public Map<String, Object> getFunctions() {
+ return functions;
+ }
+
+ /**
+ * An overridable through covariant return Expression creator.
+ * @param text the script text
+ * @param tree the parse AST tree
+ * @return the script instance
+ */
+ protected Expression createExpression(ASTJexlScript tree, String text) {
+ return new ExpressionImpl(this, text, tree);
+ }
+
+ /**
+ * Creates an Expression from a String containing valid
+ * JEXL syntax. This method parses the expression which
+ * must contain either a reference or an expression.
+ * @param expression A String containing valid JEXL syntax
+ * @return An Expression object which can be evaluated with a JexlContext
+ * @throws JexlException An exception can be thrown if there is a problem
+ * parsing this expression, or if the expression is neither an
+ * expression nor a reference.
+ */
+ public Expression createExpression(String expression) {
+ return createExpression(expression, null);
+ }
+
+ /**
+ * Creates an Expression from a String containing valid
+ * JEXL syntax. This method parses the expression which
+ * must contain either a reference or an expression.
+ * @param expression A String containing valid JEXL syntax
+ * @return An Expression object which can be evaluated with a JexlContext
+ * @param info An info structure to carry debugging information if needed
+ * @throws JexlException An exception can be thrown if there is a problem
+ * parsing this expression, or if the expression is neither an
+ * expression or a reference.
+ */
+ public Expression createExpression(String expression, JexlInfo info) {
+ // Parse the expression
+ ASTJexlScript tree = parse(expression, info);
+ if (tree.jjtGetNumChildren() > 1) {
+ logger.warn("The JEXL Expression created will be a reference"
+ + " to the first expression from the supplied script: \"" + expression + "\" ");
+ }
+ return createExpression(tree, expression);
+ }
+
+ /**
+ * Creates a Script from a String containing valid JEXL syntax.
+ * This method parses the script which validates the syntax.
+ *
+ * @param scriptText A String containing valid JEXL syntax
+ * @return A {@link Script} which can be executed using a {@link JexlContext}.
+ * @throws JexlException if there is a problem parsing the script.
+ */
+ public Script createScript(String scriptText) {
+ return createScript(scriptText, null);
+ }
+
+ /**
+ * Creates a Script from a String containing valid JEXL syntax.
+ * This method parses the script which validates the syntax.
+ *
+ * @param scriptText A String containing valid JEXL syntax
+ * @param info An info structure to carry debugging information if needed
+ * @return A {@link Script} which can be executed using a {@link JexlContext}.
+ * @throws JexlException if there is a problem parsing the script.
+ */
+ public Script createScript(String scriptText, JexlInfo info) {
+ if (scriptText == null) {
+ throw new NullPointerException("scriptText is null");
+ }
+ // Parse the expression
+ ASTJexlScript tree = parse(scriptText, info);
+ return createScript(tree, scriptText);
+ }
+
+ /**
+ * An overridable through covariant return Script creator.
+ * @param text the script text
+ * @param tree the parse AST tree
+ * @return the script instance
+ */
+ protected Script createScript(ASTJexlScript tree, String text) {
+ return new ExpressionImpl(this, text, tree);
+ }
+
+ /**
+ * Creates a Script from a {@link File} containing valid JEXL syntax.
+ * This method parses the script and validates the syntax.
+ *
+ * @param scriptFile A {@link File} containing valid JEXL syntax.
+ * Must not be null. Must be a readable file.
+ * @return A {@link Script} which can be executed with a
+ * {@link JexlContext}.
+ * @throws IOException if there is a problem reading the script.
+ * @throws JexlException if there is a problem parsing the script.
+ */
+ public Script createScript(File scriptFile) throws IOException {
+ if (scriptFile == null) {
+ throw new NullPointerException("scriptFile is null");
+ }
+ if (!scriptFile.canRead()) {
+ throw new IOException("Can't read scriptFile (" + scriptFile.getCanonicalPath() + ")");
+ }
+ BufferedReader reader = new BufferedReader(new FileReader(scriptFile));
+ JexlInfo info = null;
+ if (debug) {
+ info = createInfo(scriptFile.getName(), 0, 0);
+ }
+ return createScript(readerToString(reader), info);
+ }
+
+ /**
+ * Creates a Script from a {@link URL} containing valid JEXL syntax.
+ * This method parses the script and validates the syntax.
+ *
+ * @param scriptUrl A {@link URL} containing valid JEXL syntax.
+ * Must not be null. Must be a readable file.
+ * @return A {@link Script} which can be executed with a
+ * {@link JexlContext}.
+ * @throws IOException if there is a problem reading the script.
+ * @throws JexlException if there is a problem parsing the script.
+ */
+ public Script createScript(URL scriptUrl) throws IOException {
+ if (scriptUrl == null) {
+ throw new NullPointerException("scriptUrl is null");
+ }
+ URLConnection connection = scriptUrl.openConnection();
+
+ BufferedReader reader = new BufferedReader(
+ new InputStreamReader(connection.getInputStream()));
+ JexlInfo info = null;
+ if (debug) {
+ info = createInfo(scriptUrl.toString(), 0, 0);
+ }
+ return createScript(readerToString(reader), info);
+ }
+
+ /**
+ * Accesses properties of a bean using an expression.
+ * <p>
+ * jexl.get(myobject, "foo.bar"); should equate to
+ * myobject.getFoo().getBar(); (or myobject.getFoo().get("bar"))
+ * </p>
+ * <p>
+ * If the JEXL engine is silent, errors will be logged through its logger as warning.
+ * </p>
+ * @param bean the bean to get properties from
+ * @param expr the property expression
+ * @return the value of the property
+ * @throws JexlException if there is an error parsing the expression or during evaluation
+ */
+ public Object getProperty(Object bean, String expr) {
+ return getProperty(null, bean, expr);
+ }
+
+ /**
+ * Accesses properties of a bean using an expression.
+ * <p>
+ * If the JEXL engine is silent, errors will be logged through its logger as warning.
+ * </p>
+ * @param context the evaluation context
+ * @param bean the bean to get properties from
+ * @param expr the property expression
+ * @return the value of the property
+ * @throws JexlException if there is an error parsing the expression or during evaluation
+ */
+ public Object getProperty(JexlContext context, Object bean, String expr) {
+ if (context == null) {
+ context = EMPTY_CONTEXT;
+ }
+ // lets build 1 unique & unused identifiers wrt context
+ String r0 = "$0";
+ for (int s = 0; context.has(r0); ++s) {
+ r0 = r0 + s;
+ }
+ expr = r0 + (expr.charAt(0) == '[' ? "" : ".") + expr + ";";
+ try {
+ JexlNode tree = parse(expr, null);
+ JexlNode node = tree.jjtGetChild(0);
+ Interpreter interpreter = createInterpreter(context);
+ // ensure 4 objects in register array
+ Object[] r = {r0, bean, r0, bean};
+ interpreter.setRegisters(r);
+ return node.jjtAccept(interpreter, null);
+ } catch (JexlException xjexl) {
+ if (silent) {
+ logger.warn(xjexl.getMessage(), xjexl.getCause());
+ return null;
+ }
+ throw xjexl;
+ }
+ }
+
+ /**
+ * Assign properties of a bean using an expression.
+ * <p>
+ * jexl.set(myobject, "foo.bar", 10); should equate to
+ * myobject.getFoo().setBar(10); (or myobject.getFoo().put("bar", 10) )
+ * </p>
+ * <p>
+ * If the JEXL engine is silent, errors will be logged through its logger as warning.
+ * </p>
+ * @param bean the bean to set properties in
+ * @param expr the property expression
+ * @param value the value of the property
+ * @throws JexlException if there is an error parsing the expression or during evaluation
+ */
+ public void setProperty(Object bean, String expr, Object value) {
+ setProperty(null, bean, expr, value);
+ }
+
+ /**
+ * Assign properties of a bean using an expression.
+ * <p>
+ * If the JEXL engine is silent, errors will be logged through its logger as warning.
+ * </p>
+ * @param context the evaluation context
+ * @param bean the bean to set properties in
+ * @param expr the property expression
+ * @param value the value of the property
+ * @throws JexlException if there is an error parsing the expression or during evaluation
+ */
+ public void setProperty(JexlContext context, Object bean, String expr, Object value) {
+ if (context == null) {
+ context = EMPTY_CONTEXT;
+ }
+ // lets build 2 unique & unused identifiers wrt context
+ String r0 = "$0", r1 = "$1";
+ for (int s = 0; context.has(r0); ++s) {
+ r0 = r0 + s;
+ }
+ for (int s = 0; context.has(r1); ++s) {
+ r1 = r1 + s;
+ }
+ // synthetize expr
+ expr = r0 + (expr.charAt(0) == '[' ? "" : ".") + expr + "=" + r1 + ";";
+ try {
+ JexlNode tree = parse(expr, null);
+ JexlNode node = tree.jjtGetChild(0);
+ Interpreter interpreter = createInterpreter(context);
+ // set the registers
+ Object[] r = {r0, bean, r1, value};
+ interpreter.setRegisters(r);
+ node.jjtAccept(interpreter, null);
+ } catch (JexlException xjexl) {
+ if (silent) {
+ logger.warn(xjexl.getMessage(), xjexl.getCause());
+ return;
+ }
+ throw xjexl;
+ }
+ }
+
+ /**
+ * Invokes an object's method by name and arguments.
+ * @param obj the method's invoker object
+ * @param meth the method's name
+ * @param args the method's arguments
+ * @return the method returned value or null if it failed and engine is silent
+ * @throws JexlException if method could not be found or failed and engine is not silent
+ */
+ public Object invokeMethod(Object obj, String meth, Object... args) {
+ JexlException xjexl = null;
+ Object result = null;
+ JexlInfo info = debugInfo();
+ try {
+ JexlMethod method = uberspect.getMethod(obj, meth, args, info);
+ if (method == null && arithmetic.narrowArguments(args)) {
+ method = uberspect.getMethod(obj, meth, args, info);
+ }
+ if (method != null) {
+ result = method.invoke(obj, args);
+ } else {
+ xjexl = new JexlException(info, "failed finding method " + meth);
+ }
+ } catch (Exception xany) {
+ xjexl = new JexlException(info, "failed executing method " + meth, xany);
+ } finally {
+ if (xjexl != null) {
+ if (silent) {
+ logger.warn(xjexl.getMessage(), xjexl.getCause());
+ return null;
+ }
+ throw xjexl;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Creates a new instance of an object using the most appropriate constructor
+ * based on the arguments.
+ * @param <T> the type of object
+ * @param clazz the class to instantiate
+ * @param args the constructor arguments
+ * @return the created object instance or null on failure when silent
+ */
+ public <T> T newInstance(Class<? extends T> clazz, Object...args) {
+ return clazz.cast(doCreateInstance(clazz, args));
+ }
+
+ /**
+ * Creates a new instance of an object using the most appropriate constructor
+ * based on the arguments.
+ * @param clazz the name of the class to instantiate resolved through this engine's class loader
+ * @param args the constructor arguments
+ * @return the created object instance or null on failure when silent
+ */
+ public Object newInstance(String clazz, Object...args) {
+ return doCreateInstance(clazz, args);
+ }
+
+ /**
+ * Creates a new instance of an object using the most appropriate constructor
+ * based on the arguments.
+ * @param clazz the class to instantiate
+ * @param args the constructor arguments
+ * @return the created object instance or null on failure when silent
+ */
+ protected Object doCreateInstance(Object clazz, Object...args) {
+ JexlException xjexl = null;
+ Object result = null;
+ JexlInfo info = debugInfo();
+ try {
+ Constructor<?> ctor = uberspect.getConstructor(clazz, args, info);
+ if (ctor == null && arithmetic.narrowArguments(args)) {
+ ctor = uberspect.getConstructor(clazz, args, info);
+ }
+ if (ctor != null) {
+ result = ctor.newInstance(args);
+ } else {
+ xjexl = new JexlException(info, "failed finding constructor for " + clazz.toString());
+ }
+ } catch (Exception xany) {
+ xjexl = new JexlException(info, "failed executing constructor for " + clazz.toString(), xany);
+ } finally {
+ if (xjexl != null) {
+ if (silent) {
+ logger.warn(xjexl.getMessage(), xjexl.getCause());
+ return null;
+ }
+ throw xjexl;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Creates an interpreter.
+ * @param context a JexlContext; if null, the EMPTY_CONTEXT is used instead.
+ * @return an Interpreter
+ */
+ protected Interpreter createInterpreter(JexlContext context) {
+ if (context == null) {
+ context = EMPTY_CONTEXT;
+ }
+ return new Interpreter(this, context);
+ }
+
+ /**
+ * A soft reference on cache.
+ * <p>The cache is held through a soft reference, allowing it to be GCed under
+ * memory pressure.</p>
+ * @param <K> the cache key entry type
+ * @param <V> the cache key value type
+ */
+ protected class SoftCache<K, V> {
+ /**
+ * The cache size.
+ */
+ private final int size;
+ /**
+ * The soft reference to the cache map.
+ */
+ private SoftReference<Map<K, V>> ref = null;
+
+ /**
+ * Creates a new instance of a soft cache.
+ * @param theSize the cache size
+ */
+ SoftCache(int theSize) {
+ size = theSize;
+ }
+
+ /**
+ * Returns the cache size.
+ * @return the cache size
+ */
+ int size() {
+ return size;
+ }
+
+ /**
+ * Produces the cache entry set.
+ * @return the cache entry set
+ */
+ Set<Entry<K, V>> entrySet() {
+ Map<K, V> map = ref != null ? ref.get() : null;
+ return map != null ? map.entrySet() : Collections.<Entry<K, V>>emptySet();
+ }
+
+ /**
+ * Gets a value from cache.
+ * @param key the cache entry key
+ * @return the cache entry value
+ */
+ V get(K key) {
+ final Map<K, V> map = ref != null ? ref.get() : null;
+ return map != null ? map.get(key) : null;
+ }
+
+ /**
+ * Puts a value in cache.
+ * @param key the cache entry key
+ * @param script the cache entry value
+ */
+ void put(K key, V script) {
+ Map<K, V> map = ref != null ? ref.get() : null;
+ if (map == null) {
+ map = createCache(size);
+ ref = new SoftReference<Map<K, V>>(map);
+ }
+ map.put(key, script);
+ }
+ }
+
+ /**
+ * Creates a cache.
+ * @param <K> the key type
+ * @param <V> the value type
+ * @param cacheSize the cache size, must be > 0
+ * @return a Map usable as a cache bounded to the given size
+ */
+ protected <K, V> Map<K, V> createCache(final int cacheSize) {
+ return new java.util.LinkedHashMap<K, V>(cacheSize, LOAD_FACTOR, true) {
+ /** Serial version UID. */
+ private static final long serialVersionUID = 3801124242820219131L;
+
+ @Override
+ protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
+ return size() > cacheSize;
+ }
+ };
+ }
+
+ /**
+ * Parses an expression.
+ * @param expression the expression to parse
+ * @param info debug information structure
+ * @return the parsed tree
+ * @throws JexlException if any error occured during parsing
+ */
+ protected ASTJexlScript parse(CharSequence expression, JexlInfo info) {
+ String expr = cleanExpression(expression);
+ ASTJexlScript tree = null;
+ synchronized (parser) {
+ if (cache != null) {
+ tree = cache.get(expr);
+ if (tree != null) {
+ return tree;
+ }
+ }
+ try {
+ Reader reader = new StringReader(expr);
+ // use first calling method of JexlEngine as debug info
+ if (info == null) {
+ info = debugInfo();
+ }
+ tree = parser.parse(reader, info);
+ if (cache != null) {
+ cache.put(expr, tree);
+ }
+ } catch (TokenMgrError xtme) {
+ throw new JexlException(info, "tokenization failed", xtme);
+ } catch (ParseException xparse) {
+ throw new JexlException(info, "parsing failed", xparse);
+ }
+ }
+ return tree;
+ }
+
+ /**
+ * Creates a JexlInfo instance.
+ * @param fn url/file name
+ * @param l line number
+ * @param c column number
+ * @return a JexlInfo instance
+ */
+ protected JexlInfo createInfo(String fn, int l, int c) {
+ return new DebugInfo(fn, l, c);
+ }
+
+ /**
+ * Creates and fills up debugging information.
+ * <p>This gathers the class, method and line number of the first calling method
+ * not owned by JexlEngine, UnifiedJEXL or {Script,Expression}Factory.</p>
+ * @return an Info if debug is set, null otherwise
+ */
+ protected JexlInfo debugInfo() {
+ JexlInfo info = null;
+ if (debug) {
+ Throwable xinfo = new Throwable();
+ xinfo.fillInStackTrace();
+ StackTraceElement[] stack = xinfo.getStackTrace();
+ StackTraceElement se = null;
+ Class<?> clazz = getClass();
+ for (int s = 1; s < stack.length; ++s, se = null) {
+ se = stack[s];
+ String className = se.getClassName();
+ if (!className.equals(clazz.getName())) {
+ // go deeper if called from JexlEngine or UnifiedJEXL
+ if (className.equals(JexlEngine.class.getName())) {
+ clazz = JexlEngine.class;
+ } else if (className.equals(UnifiedJEXL.class.getName())) {
+ clazz = UnifiedJEXL.class;
+ } else {
+ break;
+ }
+ }
+ }
+ if (se != null) {
+ info = createInfo(se.getClassName() + "." + se.getMethodName(), se.getLineNumber(), 0);
+ }
+ }
+ return info;
+ }
+
+ /**
+ * Trims the expression from front & ending spaces.
+ * @param str expression to clean
+ * @return trimmed expression ending in a semi-colon
+ */
+ public static final String cleanExpression(CharSequence str) {
+ if (str != null) {
+ int start = 0;
+ int end = str.length();
+ if (end > 0) {
+ // trim front spaces
+ while (start < end && str.charAt(start) == ' ') {
+ ++start;
+ }
+ // trim ending spaces
+ while (end > 0 && str.charAt(end - 1) == ' ') {
+ --end;
+ }
+ return str.subSequence(start, end).toString();
+ }
+ return "";
+ }
+ return null;
+ }
+
+ /**
+ * Read from a reader into a StringBuffer and return a String with
+ * the contents of the reader.
+ * @param scriptReader to be read.
+ * @return the contents of the reader as a String.
+ * @throws IOException on any error reading the reader.
+ */
+ public static final String readerToString(Reader scriptReader) throws IOException {
+ StringBuilder buffer = new StringBuilder();
+ BufferedReader reader;
+ if (scriptReader instanceof BufferedReader) {
+ reader = (BufferedReader) scriptReader;
+ } else {
+ reader = new BufferedReader(scriptReader);
+ }
+ try {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ buffer.append(line).append('\n');
+ }
+ return buffer.toString();
+ } finally {
+ try {
+ reader.close();
+ } catch(IOException xio) {
+ // ignore
+ }
+ }
+
+ }
+}
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/JexlException.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/JexlException.java
new file mode 100644
index 0000000..be08fcf
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/JexlException.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+
+import org.apache.commons.jexl2.parser.JexlNode;
+
+/**
+ * Wraps any error that might occur during interpretation of a script or expression.
+ * @since 2.0
+ */
+public class JexlException extends RuntimeException {
+ /** Serial version UID. */
+ private static final long serialVersionUID = 2690666400232612395L;
+ /** The point of origin for this exception. */
+ protected final JexlNode mark;
+ /** The debug info. */
+ protected final JexlInfo info;
+ /** A marker to use in NPEs stating a null operand error. */
+ public static final String NULL_OPERAND = "jexl.null";
+ /**
+ * Creates a new JexlException.
+ * @param node the node causing the error
+ * @param msg the error message
+ */
+ public JexlException(JexlNode node, String msg) {
+ super(msg);
+ mark = node;
+ info = node != null? node.getInfo() : null;
+
+ }
+
+ /**
+ * Creates a new JexlException.
+ * @param node the node causing the error
+ * @param msg the error message
+ * @param cause the exception causing the error
+ */
+ public JexlException(JexlNode node, String msg, Throwable cause) {
+ super(msg, cause);
+ mark = node;
+ info = node != null? node.getInfo() : null;
+ }
+
+ /**
+ * Creates a new JexlException.
+ * @param dbg the debugging information associated
+ * @param msg the error message
+ */
+ public JexlException(JexlInfo dbg, String msg) {
+ super(msg);
+ mark = null;
+ info = dbg;
+ }
+
+ /**
+ * Creates a new JexlException.
+ * @param dbg the debugging information associated
+ * @param msg the error message
+ * @param cause the exception causing the error
+ */
+ public JexlException(JexlInfo dbg, String msg, Throwable cause) {
+ super(msg, cause);
+ mark = null;
+ info = dbg;
+ }
+
+ /**
+ * Gets information about the cause of this error.
+ * <p>
+ * The returned string represents the outermost expression in error.
+ * The info parameter, an int[2] optionally provided by the caller, will be filled with the begin/end offset
+ * characters of the precise error's trigger.
+ * </p>
+ * @param offsets character offset interval of the precise node triggering the error
+ * @return a string representation of the offending expression, the empty string if it could not be determined
+ */
+ public String getInfo(int[] offsets) {
+ Debugger dbg = new Debugger();
+ if (dbg.debug(mark)) {
+ if (offsets != null && offsets.length >= 2) {
+ offsets[0] = dbg.start();
+ offsets[1] = dbg.end();
+ }
+ return dbg.data();
+ }
+ return "";
+ }
+
+ /**
+ * Detailed info message about this error.
+ * Format is "debug![begin,end]: string \n msg" where:
+ * - debug is the debugging information if it exists (@link JexlEngine.setDebug)
+ * - begin, end are character offsets in the string for the precise location of the error
+ * - string is the string representation of the offending expression
+ * - msg is the actual explanation message for this error
+ * @return this error as a string
+ */
+ @Override
+ public String getMessage() {
+ Debugger dbg = new Debugger();
+ StringBuilder msg = new StringBuilder();
+ if (info != null) {
+ msg.append(info.debugString());
+ }
+ if (dbg.debug(mark)) {
+ msg.append("![");
+ msg.append(dbg.start());
+ msg.append(",");
+ msg.append(dbg.end());
+ msg.append("]: '");
+ msg.append(dbg.data());
+ msg.append("' ");
+ }
+ msg.append(super.getMessage());
+ Throwable cause = getCause();
+ if (cause != null && NULL_OPERAND == cause.getMessage()) {
+ msg.append(" caused by null operand");
+ }
+ return msg.toString();
+ }
+}
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/JexlInfo.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/JexlInfo.java
new file mode 100644
index 0000000..86d309a
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/JexlInfo.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+
+/**
+ * Interface for objects carrying information usefull to debugging.
+ * @since 1.0
+ */
+public interface JexlInfo {
+ /**
+ * Formats this information for debugging purpose.
+ * @return a human readable string.
+ */
+ String debugString();
+}
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/Main.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/Main.java
new file mode 100644
index 0000000..a040319
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/Main.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStreamReader;
+
+/**
+ * Test application for JEXL.
+ *
+ * @since 2.0
+ */
+public class Main {
+ /**
+ * Test application for JEXL
+ *
+ * If a single argument is present, it is treated as a filename of a JEXL
+ * script to be executed as a script. Any exceptions terminate the application.
+ *
+ * Otherwise, lines are read from standard input and evaluated.
+ * ParseExceptions and JexlExceptions are logged, and do not cause the application to exit.
+ * This is done so that interactive testing is easier.
+ *
+ * @param args (optional) filename to execute. Stored in the args variable.
+ *
+ * @throws Exception if parsing or IO fail
+ */
+ public static void main(String[] args) throws Exception {
+ JexlEngine engine = new JexlEngine();
+ JexlContext context = new MapContext();
+ context.set("args", args);
+ if (args.length == 1) {
+ Script script = engine.createScript(new File(args[0]));
+ Object value = script.execute(context);
+ System.out.println("Return value: " + value);
+ } else {
+ BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
+ String line;
+ System.out.print("> ");
+ while (null != (line = console.readLine())) {
+ try {
+ Expression expression = engine.createExpression(line);
+ Object value = expression.evaluate(context);
+ System.out.println("Return value: " + value);
+ } catch (JexlException e) {
+ System.out.println(e.getLocalizedMessage());
+ }
+ System.out.print("> ");
+ }
+ }
+ }
+}
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/MapContext.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/MapContext.java
new file mode 100644
index 0000000..123cfc4
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/MapContext.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Wraps a map in a context.
+ * <p>Each entry in the map is considered a variable name, value pair.</p>
+ */
+public class MapContext implements JexlContext {
+ /**
+ * The wrapped variable map.
+ */
+ protected final Map<String, Object> map;
+
+ /**
+ * Creates a MapContext on an automatically allocated underlying HashMap.
+ */
+ public MapContext() {
+ this(null);
+ }
+
+ /**
+ * Creates a MapContext wrapping an existing user provided map.
+ * @param vars the variable map
+ */
+ public MapContext(Map<String, Object> vars) {
+ super();
+ map = vars == null ? new HashMap<String, Object>() : vars;
+ }
+
+ /** {@inheritDoc} */
+ public boolean has(String name) {
+ return map.containsKey(name);
+ }
+
+ /** {@inheritDoc} */
+ public Object get(String name) {
+ return map.get(name);
+ }
+
+ /** {@inheritDoc} */
+ public void set(String name, Object value) {
+ map.put(name, value);
+ }
+}
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/Script.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/Script.java
new file mode 100644
index 0000000..0b9892b
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/Script.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+
+/**
+ * <p>A JEXL Script.</p>
+ * <p>A script is some valid JEXL syntax to be executed with
+ * a given set of {@link JexlContext} variabless.</p>
+ * <p>A script is a group of statements, separated by semicolons.</p>
+ * <p>The statements can be <code>blocks</code> (curly braces containing code),
+ * Control statements such as <code>if</code> and <code>while</code>
+ * as well as expressions and assignment statements.</p>
+ *
+ * @since 1.1
+ */
+public interface Script {
+ /**
+ * Executes the script with the variables contained in the
+ * supplied {@link JexlContext}.
+ *
+ * @param context A JexlContext containing variables.
+ * @return The result of this script, usually the result of
+ * the last statement.
+ */
+ Object execute(JexlContext context);
+
+ /**
+ * Returns the text of this Script.
+ * @return The script to be executed.
+ */
+ String getText();
+
+}
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/UnifiedJEXL.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/UnifiedJEXL.java
new file mode 100644
index 0000000..76942cf
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/UnifiedJEXL.java
@@ -0,0 +1,986 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+
+import java.util.ArrayList;
+import org.apache.commons.jexl2.parser.JexlNode;
+import org.apache.commons.jexl2.parser.StringParser;
+
+/**
+ * An evaluator similar to the Unified EL evaluator used in JSP/JSF based on JEXL.
+ * It is intended to be used in configuration modules, XML based frameworks or JSP taglibs
+ * and facilitate the implementation of expression evaluation.
+ * <p>
+ * An expression can mix immediate, deferred and nested sub-expressions as well as string constants;
+ * <ul>
+ * <li>The "immediate" syntax is of the form <code>"...${jexl-expr}..."</code></li>
+ * <li>The "deferred" syntax is of the form <code>"...#{jexl-expr}..."</code></li>
+ * <li>The "nested" syntax is of the form <code>"...#{...${jexl-expr0}...}..."</code></li>
+ * <li>The "composite" syntax is of the form <code>"...${jexl-expr0}... #{jexl-expr1}..."</code></li>
+ * </ul>
+ * </p>
+ * <p>
+ * Deferred & immediate expression carry different intentions:
+ * <ul>
+ * <li>An immediate expression indicate that evaluation is intended to be performed close to
+ * the definition/parsing point.</li>
+ * <li>A deferred expression indicate that evaluation is intended to occur at a later stage.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * For instance: <code>"Hello ${name}, now is #{time}"</code> is a composite "deferred" expression since one
+ * of its subexpressions is deferred. Furthermore, this (composite) expression intent is
+ * to perform two evaluations; one close to its definition and another one in a later
+ * phase.
+ * </p>
+ * <p>
+ * The API reflects this feature in 2 methods, prepare and evaluate. The prepare method
+ * will evaluate the immediate subexpression and return an expression that contains only
+ * the deferred subexpressions (& constants), a prepared expression. Such a prepared expression
+ * is suitable for a later phase evaluation that may occur with a different JexlContext.
+ * Note that it is valid to call evaluate without prepare in which case the same JexlContext
+ * is used for the 2 evaluation phases.
+ * </p>
+ * <p>
+ * In the most common use-case where deferred expressions are to be kept around as properties of objects,
+ * one should parse & prepare an expression before storing it and evaluate it each time
+ * the property storing it is accessed.
+ * </p>
+ * <p>
+ * Note that nested expression use the JEXL syntax as in:
+ * <code>"#{${bar}+'.charAt(2)'}"</code>
+ * The most common mistake leading to an invalid expression being the following:
+ * <code>"#{${bar}charAt(2)}"</code>
+ * </p>
+ * <p>Also note that methods that parse evaluate expressions may throw <em>unchecked</em> ecxeptions;
+ * The {@link UnifiedJEXL.Exception} are thrown when the engine instance is in "non-silent" mode
+ * but since these are RuntimeException, user-code <em>should</em> catch them where appropriate.
+ * </p>
+ * @since 2.0
+ */
+public final class UnifiedJEXL {
+ /** The JEXL engine instance. */
+ private final JexlEngine jexl;
+ /** The expression cache. */
+ private final JexlEngine.SoftCache<String,Expression> cache;
+ /** The default cache size. */
+ private static final int CACHE_SIZE = 256;
+ /**
+ * Creates a new instance of UnifiedJEXL with a default size cache.
+ * @param aJexl the JexlEngine to use.
+ */
+ public UnifiedJEXL(JexlEngine aJexl) {
+ this(aJexl, CACHE_SIZE);
+ }
+
+ /**
+ * Creates a new instance of UnifiedJEXL creating a local cache.
+ * @param aJexl the JexlEngine to use.
+ * @param cacheSize the number of expressions in this cache
+ */
+ public UnifiedJEXL(JexlEngine aJexl, int cacheSize) {
+ this.jexl = aJexl;
+ this.cache = aJexl.new SoftCache<String,Expression>(cacheSize);
+ }
+
+ /**
+ * Types of expressions.
+ * Each instance carries a counter index per (composite sub-) expression type.
+ * @see ExpressionBuilder
+ */
+ private static enum ExpressionType {
+ /** Constant expression, count index 0. */
+ CONSTANT(0),
+ /** Immediate expression, count index 1. */
+ IMMEDIATE(1),
+ /** Deferred expression, count index 2. */
+ DEFERRED(2),
+ /** Nested (which are deferred) expressions, count index 2. */
+ NESTED(2),
+ /** Composite expressions are not counted, index -1. */
+ COMPOSITE(-1);
+ /** The index in arrays of expression counters for composite expressions. */
+ private final int index;
+ /**
+ * Creates an ExpressionType.
+ * @param idx the index for this type in counters arrays.
+ */
+ ExpressionType(int idx) {
+ this.index = idx;
+ }
+ }
+
+ /**
+ * A helper class to build expressions.
+ * Keeps count of sub-expressions by type.
+ */
+ private static class ExpressionBuilder {
+ /** Per expression type counters. */
+ private final int[] counts;
+ /** The list of expressions. */
+ private final ArrayList<Expression> expressions;
+
+ /**
+ * Creates a builder.
+ * @param size the initial expression array size
+ */
+ ExpressionBuilder(int size) {
+ counts = new int[]{0, 0, 0};
+ expressions = new ArrayList<Expression>(size <= 0 ? 3 : size);
+ }
+
+ /**
+ * Adds an expression to the list of expressions, maintain per-type counts.
+ * @param expr the expression to add
+ */
+ void add(Expression expr) {
+ counts[expr.getType().index] += 1;
+ expressions.add(expr);
+ }
+
+ /**
+ * Builds an expression from a source, performs checks.
+ * @param el the unified el instance
+ * @param source the source expression
+ * @return an expression
+ */
+ Expression build(UnifiedJEXL el, Expression source) {
+ int sum = 0;
+ for (int count : counts) {
+ sum += count;
+ }
+ if (expressions.size() != sum) {
+ StringBuilder error = new StringBuilder("parsing algorithm error, exprs: ");
+ error.append(expressions.size());
+ error.append(", constant:");
+ error.append(counts[ExpressionType.CONSTANT.index]);
+ error.append(", immediate:");
+ error.append(counts[ExpressionType.IMMEDIATE.index]);
+ error.append(", deferred:");
+ error.append(counts[ExpressionType.DEFERRED.index]);
+ throw new IllegalStateException(error.toString());
+ }
+ // if only one sub-expr, no need to create a composite
+ if (expressions.size() == 1) {
+ return expressions.get(0);
+ } else {
+ return el.new CompositeExpression(counts, expressions, source);
+ }
+ }
+ }
+
+ /**
+ * Gets the JexlEngine underlying the UnifiedJEXL.
+ * @return the JexlEngine
+ */
+ public JexlEngine getEngine() {
+ return jexl;
+ }
+
+ /**
+ * The sole type of (runtime) exception the UnifiedJEXL can throw.
+ */
+ public static class Exception extends RuntimeException {
+ /** Serial version UID. */
+ private static final long serialVersionUID = -8201402995815975726L;
+ /**
+ * Creates a UnifiedJEXL.Exception.
+ * @param msg the exception message
+ * @param cause the exception cause
+ */
+ public Exception(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+ }
+
+ /**
+ * The abstract base class for all expressions, immediate '${...}' and deferred '#{...}'.
+ */
+ public abstract class Expression {
+ /** The source of this expression (see {@link UnifiedJEXL.Expression#prepare}). */
+ protected final Expression source;
+ /**
+ * Creates an expression.
+ * @param src the source expression if any
+ */
+ Expression(Expression src) {
+ this.source = src != null ? src : this;
+ }
+
+ /**
+ * Formats this expression, adding its source string representation in
+ * comments if available: 'expression /*= source *\/'' .
+ * @return the formatted expression string
+ */
+ @Override
+ public String toString() {
+ StringBuilder strb = new StringBuilder();
+ if (source != this) {
+ strb.append(source.toString());
+ strb.append(" /*= ");
+ }
+ asString(strb);
+ if (source != this) {
+ strb.append(" */");
+ }
+ return strb.toString();
+ }
+
+ /**
+ * Generates this expression's string representation.
+ * @return the string representation
+ */
+ public String asString() {
+ StringBuilder strb = new StringBuilder();
+ asString(strb);
+ return strb.toString();
+ }
+
+ /**
+ * Adds this expression's string representation to a StringBuilder.
+ * @param strb the builder to fill
+ */
+ abstract void asString(StringBuilder strb);
+
+ /**
+ * When the expression is dependant upon immediate and deferred sub-expressions,
+ * evaluates the immediate sub-expressions with the context passed as parameter
+ * and returns this expression deferred form.
+ * <p>
+ * In effect, this binds the result of the immediate sub-expressions evaluation in the
+ * context, allowing to differ evaluation of the remaining (deferred) expression within another context.
+ * This only has an effect to nested & composite expressions that contain differed & immediate sub-expressions.
+ * </p>
+ * <p>
+ * If the underlying JEXL engine is silent, errors will be logged through its logger as warning.
+ * </p>
+ * @param context the context to use for immediate expression evaluations
+ * @return an expression or null if an error occurs and the {@link JexlEngine} is silent
+ * @throws UnifiedJEXL.Exception if an error occurs and the {@link JexlEngine} is not silent
+ */
+ public abstract Expression prepare(JexlContext context);
+
+ /**
+ * Evaluates this expression.
+ * <p>
+ * If the underlying JEXL engine is silent, errors will be logged through its logger as warning.
+ * </p>
+ * @param context the variable context
+ * @return the result of this expression evaluation or null if an error occurs and the {@link JexlEngine} is
+ * silent
+ * @throws UnifiedJEXL.Exception if an error occurs and the {@link JexlEngine} is not silent
+ */
+ public abstract Object evaluate(JexlContext context);
+
+ /**
+ * Checks whether this expression is immediate.
+ * @return true if immediate, false otherwise
+ */
+ public boolean isImmediate() {
+ return true;
+ }
+
+ /**
+ * Checks whether this expression is deferred.
+ * @return true if deferred, false otherwise
+ */
+ public final boolean isDeferred() {
+ return !isImmediate();
+ }
+
+ /**
+ * Retrieves this expression's source expression.
+ * If this expression was prepared, this allows to retrieve the
+ * original expression that lead to it.
+ * Other expressions return themselves.
+ * @return the source expression
+ */
+ public final Expression getSource() {
+ return source;
+ }
+
+ /**
+ * Gets this expression type.
+ * @return its type
+ */
+ abstract ExpressionType getType();
+
+ /**
+ * Prepares a sub-expression for interpretation.
+ * @param interpreter a JEXL interpreter
+ * @return a prepared expression
+ * @throws JexlException (only for nested & composite)
+ */
+ abstract Expression prepare(Interpreter interpreter);
+
+ /**
+ * Intreprets a sub-expression.
+ * @param interpreter a JEXL interpreter
+ * @return the result of interpretation
+ * @throws JexlException (only for nested & composite)
+ */
+ abstract Object evaluate(Interpreter interpreter);
+ }
+
+
+ /** A constant expression. */
+ private class ConstantExpression extends Expression {
+ /** The constant held by this expression. */
+ private final Object value;
+ /**
+ * Creates a constant expression.
+ * <p>
+ * If the wrapped constant is a string, it is treated
+ * as a JEXL strings with respect to escaping.
+ * </p>
+ * @param val the constant value
+ * @param source the source expression if any
+ */
+ ConstantExpression(Object val, Expression source) {
+ super(source);
+ if (val == null) {
+ throw new NullPointerException("constant can not be null");
+ }
+ if (val instanceof String) {
+ val = StringParser.buildString((String) val, false);
+ }
+ this.value = val;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String asString() {
+ StringBuilder strb = new StringBuilder();
+ strb.append('"');
+ asString(strb);
+ strb.append('"');
+ return strb.toString();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ ExpressionType getType() {
+ return ExpressionType.CONSTANT;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ void asString(StringBuilder strb) {
+ String str = value.toString();
+ if (value instanceof String || value instanceof CharSequence) {
+ for (int i = 0, size = str.length(); i < size; ++i) {
+ char c = str.charAt(i);
+ if (c == '"' || c == '\\') {
+ strb.append('\\');
+ }
+ strb.append(c);
+ }
+ } else {
+ strb.append(str);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Expression prepare(JexlContext context) {
+ return this;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ Expression prepare(Interpreter interpreter) {
+ return this;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object evaluate(JexlContext context) {
+ return value;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ Object evaluate(Interpreter interpreter) {
+ return value;
+ }
+ }
+
+
+ /** The base for Jexl based expressions. */
+ private abstract class JexlBasedExpression extends Expression {
+ /** The JEXL string for this expression. */
+ protected final CharSequence expr;
+ /** The JEXL node for this expression. */
+ protected final JexlNode node;
+ /**
+ * Creates a JEXL interpretable expression.
+ * @param theExpr the expression as a string
+ * @param theNode the expression as an AST
+ * @param theSource the source expression if any
+ */
+ protected JexlBasedExpression(CharSequence theExpr, JexlNode theNode, Expression theSource) {
+ super(theSource);
+ this.expr = theExpr;
+ this.node = theNode;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ StringBuilder strb = new StringBuilder(expr.length() + 3);
+ if (source != this) {
+ strb.append(source.toString());
+ strb.append(" /*= ");
+ }
+ strb.append(isImmediate() ? '$' : '#');
+ strb.append("{");
+ strb.append(expr);
+ strb.append("}");
+ if (source != this) {
+ strb.append(" */");
+ }
+ return strb.toString();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void asString(StringBuilder strb) {
+ strb.append(isImmediate() ? '$' : '#');
+ strb.append("{");
+ strb.append(expr);
+ strb.append("}");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Expression prepare(JexlContext context) {
+ return this;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ Expression prepare(Interpreter interpreter) {
+ return this;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object evaluate(JexlContext context) {
+ return UnifiedJEXL.this.evaluate(context, this);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ Object evaluate(Interpreter interpreter) {
+ return interpreter.interpret(node);
+ }
+ }
+
+
+ /** An immediate expression: ${jexl}. */
+ private class ImmediateExpression extends JexlBasedExpression {
+ /**
+ * Creates an immediate expression.
+ * @param expr the expression as a string
+ * @param node the expression as an AST
+ * @param source the source expression if any
+ */
+ ImmediateExpression(CharSequence expr, JexlNode node, Expression source) {
+ super(expr, node, source);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ ExpressionType getType() {
+ return ExpressionType.IMMEDIATE;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isImmediate() {
+ return true;
+ }
+ }
+
+ /** An immediate expression: ${jexl}. */
+ private class DeferredExpression extends JexlBasedExpression {
+ /**
+ * Creates a deferred expression.
+ * @param expr the expression as a string
+ * @param node the expression as an AST
+ * @param source the source expression if any
+ */
+ DeferredExpression(CharSequence expr, JexlNode node, Expression source) {
+ super(expr, node, source);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ ExpressionType getType() {
+ return ExpressionType.DEFERRED;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isImmediate() {
+ return false;
+ }
+ }
+
+ /**
+ * A deferred expression that nests an immediate expression.
+ * #{...${jexl}...}
+ * Note that the deferred syntax is JEXL's, not UnifiedJEXL.
+ */
+ private class NestedExpression extends DeferredExpression {
+ /**
+ * Creates a nested expression.
+ * @param expr the expression as a string
+ * @param node the expression as an AST
+ * @param source the source expression if any
+ */
+ NestedExpression(CharSequence expr, JexlNode node, Expression source) {
+ super(expr, node, source);
+ if (this.source != this) {
+ throw new IllegalArgumentException("Nested expression can not have a source");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ ExpressionType getType() {
+ return ExpressionType.NESTED;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return expr.toString();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Expression prepare(JexlContext context) {
+ return UnifiedJEXL.this.prepare(context, this);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Expression prepare(Interpreter interpreter) {
+ String value = interpreter.interpret(node).toString();
+ JexlNode dnode = toNode(value, jexl.isDebug()? node.getInfo() : null);
+ return new DeferredExpression(value, dnode, this);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object evaluate(Interpreter interpreter) {
+ return prepare(interpreter).evaluate(interpreter);
+ }
+ }
+
+
+ /** A composite expression: "... ${...} ... #{...} ...". */
+ private class CompositeExpression extends Expression {
+ /** Bit encoded (deferred count > 0) bit 1, (immediate count > 0) bit 0. */
+ private final int meta;
+ /** The list of sub-expression resulting from parsing. */
+ private final Expression[] exprs;
+ /**
+ * Creates a composite expression.
+ * @param counters counters of expression per type
+ * @param list the sub-expressions
+ * @param src the source for this expresion if any
+ */
+ CompositeExpression(int[] counters, ArrayList<Expression> list, Expression src) {
+ super(src);
+ this.exprs = list.toArray(new Expression[list.size()]);
+ this.meta = (counters[ExpressionType.DEFERRED.index] > 0 ? 2 : 0)
+ | (counters[ExpressionType.IMMEDIATE.index] > 0 ? 1 : 0);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ ExpressionType getType() {
+ return ExpressionType.COMPOSITE;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isImmediate() {
+ // immediate if no deferred
+ return (meta & 2) == 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ void asString(StringBuilder strb) {
+ for (Expression e : exprs) {
+ e.asString(strb);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Expression prepare(JexlContext context) {
+ return UnifiedJEXL.this.prepare(context, this);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ Expression prepare(Interpreter interpreter) {
+ // if this composite is not its own source, it is already prepared
+ if (source != this) {
+ return this;
+ }
+ // we need to eval immediate expressions if there are some deferred/nested
+ // ie both immediate & deferred counts > 0, bits 1 & 0 set, (1 << 1) & 1 == 3
+ final boolean evalImmediate = meta == 3;
+ final int size = exprs.length;
+ final ExpressionBuilder builder = new ExpressionBuilder(size);
+ // tracking whether prepare will return a different expression
+ boolean eq = true;
+ for (int e = 0; e < size; ++e) {
+ Expression expr = exprs[e];
+ Expression prepared = expr.prepare(interpreter);
+ if (evalImmediate && prepared instanceof ImmediateExpression) {
+ // evaluate immediate as constant
+ Object value = prepared.evaluate(interpreter);
+ prepared = value == null ? null : new ConstantExpression(value, prepared);
+ }
+ // add it if not null
+ if (prepared != null) {
+ builder.add(prepared);
+ }
+ // keep track of expression equivalence
+ eq &= expr == prepared;
+ }
+ Expression ready = eq ? this : builder.build(UnifiedJEXL.this, this);
+ return ready;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object evaluate(JexlContext context) {
+ return UnifiedJEXL.this.evaluate(context, this);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ Object evaluate(Interpreter interpreter) {
+ final int size = exprs.length;
+ Object value = null;
+ // common case: evaluate all expressions & concatenate them as a string
+ StringBuilder strb = new StringBuilder();
+ for (int e = 0; e < size; ++e) {
+ value = exprs[e].evaluate(interpreter);
+ if (value != null) {
+ strb.append(value.toString());
+ }
+ }
+ value = strb.toString();
+ return value;
+ }
+ }
+
+ /** Creates a a {@link UnifiedJEXL.Expression} from an expression string.
+ * Uses & fills up the expression cache if any.
+ * <p>
+ * If the underlying JEXL engine is silent, errors will be logged through its logger as warnings.
+ * </p>
+ * @param expression the UnifiedJEXL string expression
+ * @return the UnifiedJEXL object expression, null if silent and an error occured
+ * @throws UnifiedJEXL.Exception if an error occurs and the {@link JexlEngine} is not silent
+ */
+ public Expression parse(String expression) {
+ Exception xuel = null;
+ Expression stmt = null;
+ try {
+ if (cache == null) {
+ stmt = parseExpression(expression);
+ } else {
+ synchronized (cache) {
+ stmt = cache.get(expression);
+ if (stmt == null) {
+ stmt = parseExpression(expression);
+ cache.put(expression, stmt);
+ }
+ }
+ }
+ } catch (JexlException xjexl) {
+ xuel = new Exception("failed to parse '" + expression + "'", xjexl);
+ } catch (Exception xany) {
+ xuel = xany;
+ } finally {
+ if (xuel != null) {
+ if (jexl.isSilent()) {
+ jexl.logger.warn(xuel.getMessage(), xuel.getCause());
+ return null;
+ }
+ throw xuel;
+ }
+ }
+ return stmt;
+ }
+
+ /**
+ * Prepares an expression (nested & composites), handles exception reporting.
+ *
+ * @param context the JEXL context to use
+ * @param expr the expression to prepare
+ * @return a prepared expression
+ * @throws UnifiedJEXL.Exception if an error occurs and the {@link JexlEngine} is not silent
+ */
+ Expression prepare(JexlContext context, Expression expr) {
+ try {
+ Interpreter interpreter = jexl.createInterpreter(context);
+ interpreter.setSilent(false);
+ return expr.prepare(interpreter);
+ } catch (JexlException xjexl) {
+ Exception xuel = createException("prepare", expr, xjexl);
+ if (jexl.isSilent()) {
+ jexl.logger.warn(xuel.getMessage(), xuel.getCause());
+ return null;
+ }
+ throw xuel;
+ }
+ }
+
+ /**
+ * Evaluates an expression (nested & composites), handles exception reporting.
+ *
+ * @param context the JEXL context to use
+ * @param expr the expression to prepare
+ * @return the result of the evaluation
+ * @throws UnifiedJEXL.Exception if an error occurs and the {@link JexlEngine} is not silent
+ */
+ Object evaluate(JexlContext context, Expression expr) {
+ try {
+ Interpreter interpreter = jexl.createInterpreter(context);
+ interpreter.setSilent(false);
+ return expr.evaluate(interpreter);
+ } catch (JexlException xjexl) {
+ Exception xuel = createException("evaluate", expr, xjexl);
+ if (jexl.isSilent()) {
+ jexl.logger.warn(xuel.getMessage(), xuel.getCause());
+ return null;
+ }
+ throw xuel;
+ }
+ }
+
+ /**
+ * Use the JEXL parser to create the AST for an expression.
+ * @param expression the expression to parse
+ * @return the AST
+ * @throws JexlException if an error occur during parsing
+ */
+ private JexlNode toNode(CharSequence expression) {
+ return jexl.parse(expression, null);
+ }
+
+ /**
+ * Use the JEXL parser to create the AST for an expression.
+ * @param expression the expression to parse
+ * @param info debug information
+ * @return the AST
+ * @throws JexlException if an error occur during parsing
+ */
+ private JexlNode toNode(CharSequence expression, JexlInfo info) {
+ return jexl.parse(expression, info);
+ }
+
+ /**
+ * Creates a UnifiedJEXL.Exception from a JexlException.
+ * @param action parse, prepare, evaluate
+ * @param expr the expression
+ * @param xany the exception
+ * @return an exception containing an explicit error message
+ */
+ private Exception createException(String action, Expression expr, java.lang.Exception xany) {
+ StringBuilder strb = new StringBuilder("failed to ");
+ strb.append(action);
+ strb.append(" '");
+ strb.append(expr.toString());
+ strb.append("'");
+ Throwable cause = xany.getCause();
+ if (cause != null) {
+ String causeMsg = cause.getMessage();
+ if (causeMsg != null) {
+ strb.append(", ");
+ strb.append(causeMsg);
+ }
+ }
+ return new Exception(strb.toString(), xany);
+ }
+
+
+ /** The different parsing states. */
+ private static enum ParseState {
+ /** Parsing a constant. */
+ CONST,
+ /** Parsing after $ .*/
+ IMMEDIATE0,
+ /** Parsing after # .*/
+ DEFERRED0,
+ /** Parsing after ${ .*/
+ IMMEDIATE1,
+ /** Parsing after #{ .*/
+ DEFERRED1,
+ /** Parsing after \ .*/
+ ESCAPE
+ }
+
+ /**
+ * Parses a unified expression.
+ * @param expr the string expression
+ * @return the expression instance
+ * @throws JexlException if an error occur during parsing
+ */
+ private Expression parseExpression(String expr) {
+ final int size = expr.length();
+ ExpressionBuilder builder = new ExpressionBuilder(0);
+ StringBuilder strb = new StringBuilder(size);
+ ParseState state = ParseState.CONST;
+ int inner = 0;
+ boolean nested = false;
+ int inested = -1;
+ for (int i = 0; i < size; ++i) {
+ char c = expr.charAt(i);
+ switch (state) {
+ default: // in case we ever add new expression type
+ throw new UnsupportedOperationException("unexpected expression type");
+ case CONST:
+ if (c == '$') {
+ state = ParseState.IMMEDIATE0;
+ } else if (c == '#') {
+ inested = i;
+ state = ParseState.DEFERRED0;
+ } else if (c == '\\') {
+ state = ParseState.ESCAPE;
+ } else {
+ // do buildup expr
+ strb.append(c);
+ }
+ break;
+ case IMMEDIATE0: // $
+ if (c == '{') {
+ state = ParseState.IMMEDIATE1;
+ // if chars in buffer, create constant
+ if (strb.length() > 0) {
+ Expression cexpr = new ConstantExpression(strb.toString(), null);
+ builder.add(cexpr);
+ strb.delete(0, Integer.MAX_VALUE);
+ }
+ } else {
+ // revert to CONST
+ strb.append('$');
+ strb.append(c);
+ state = ParseState.CONST;
+ }
+ break;
+ case DEFERRED0: // #
+ if (c == '{') {
+ state = ParseState.DEFERRED1;
+ // if chars in buffer, create constant
+ if (strb.length() > 0) {
+ Expression cexpr = new ConstantExpression(strb.toString(), null);
+ builder.add(cexpr);
+ strb.delete(0, Integer.MAX_VALUE);
+ }
+ } else {
+ // revert to CONST
+ strb.append('#');
+ strb.append(c);
+ state = ParseState.CONST;
+ }
+ break;
+ case IMMEDIATE1: // ${...
+ if (c == '}') {
+ // materialize the immediate expr
+ Expression iexpr = new ImmediateExpression(strb.toString(), toNode(strb), null);
+ builder.add(iexpr);
+ strb.delete(0, Integer.MAX_VALUE);
+ state = ParseState.CONST;
+ } else {
+ // do buildup expr
+ strb.append(c);
+ }
+ break;
+ case DEFERRED1: // #{...
+ // skip inner strings (for '}')
+ if (c == '"' || c == '\'') {
+ strb.append(c);
+ i = StringParser.readString(strb, expr, i + 1, c);
+ continue;
+ }
+ // nested immediate in deferred; need to balance count of '{' & '}'
+ if (c == '{') {
+ if (expr.charAt(i - 1) == '$') {
+ inner += 1;
+ strb.deleteCharAt(strb.length() - 1);
+ nested = true;
+ }
+ continue;
+ }
+ // closing '}'
+ if (c == '}') {
+ // balance nested immediate
+ if (inner > 0) {
+ inner -= 1;
+ } else {
+ // materialize the nested/deferred expr
+ Expression dexpr = null;
+ if (nested) {
+ dexpr = new NestedExpression(expr.substring(inested, i + 1), toNode(strb), null);
+ } else {
+ dexpr = new DeferredExpression(strb.toString(), toNode(strb), null);
+ }
+ builder.add(dexpr);
+ strb.delete(0, Integer.MAX_VALUE);
+ nested = false;
+ state = ParseState.CONST;
+ }
+ } else {
+ // do buildup expr
+ strb.append(c);
+ }
+ break;
+ case ESCAPE:
+ if (c == '#') {
+ strb.append('#');
+ } else if (c == '$') {
+ strb.append('$');
+ } else {
+ strb.append('\\');
+ strb.append(c);
+ }
+ state = ParseState.CONST;
+ }
+ }
+ // we should be in that state
+ if (state != ParseState.CONST) {
+ throw new Exception("malformed expression: " + expr, null);
+ }
+ // if any chars were buffered, add them as a constant
+ if (strb.length() > 0) {
+ Expression cexpr = new ConstantExpression(strb.toString(), null);
+ builder.add(cexpr);
+ }
+ return builder.build(this, null);
+ }
+}
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/AbstractExecutor.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/AbstractExecutor.java
new file mode 100644
index 0000000..f945597
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/AbstractExecutor.java
@@ -0,0 +1,378 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2.internal;
+import org.apache.commons.jexl2.internal.introspection.MethodKey;
+import org.apache.commons.jexl2.introspection.JexlMethod;
+import org.apache.commons.jexl2.introspection.JexlPropertySet;
+import org.apache.commons.jexl2.introspection.JexlPropertyGet;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Abstract class that is used to execute an arbitrary
+ * method that is introspected. This is the superclass
+ * for all other AbstractExecutor classes.
+ *
+ * @since 1.0
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public abstract class AbstractExecutor {
+ /** A marker for invocation failures in tryInvoke. */
+ public static final Object TRY_FAILED = new Object() {
+ @Override
+ public String toString() {
+ return "tryExecute failed";
+ }
+ };
+
+ /**
+ * A helper to initialize the marker methods (array.get, list.get, etc...).
+ * @param clazz the class to introspect
+ * @param name the name of the method
+ * @param parms the parameters
+ * @return the method
+ */
+ static java.lang.reflect.Method initMarker(Class<?> clazz, String name, Class<?>... parms) {
+ try {
+ return clazz.getMethod(name, parms);
+ } catch (Exception xnever) {
+ throw new Error(xnever);
+ }
+ }
+
+ /**
+ * Creates an arguments array.
+ * @param args the list of arguments
+ * @return the arguments array
+ */
+ static Object[] makeArgs(Object... args) {
+ return args;
+ }
+
+ /** The class this executor applies to. */
+ protected final Class<?> objectClass;
+ /** Method to be executed. */
+ protected final java.lang.reflect.Method method;
+
+ /**
+ * Default and sole constructor.
+ * @param theClass the class this executor applies to
+ * @param theMethod the method held by this executor
+ */
+ protected AbstractExecutor(Class<?> theClass, java.lang.reflect.Method theMethod) {
+ objectClass = theClass;
+ method = theMethod;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean equals(Object obj) {
+ return this == obj || (obj instanceof AbstractExecutor && equals((AbstractExecutor) obj));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return method.hashCode();
+ }
+
+ /**
+ * Indicates whether some other executor is equivalent to this one.
+ * @param arg the other executor to check
+ * @return true if both executors are equivalent, false otherwise
+ */
+ public boolean equals(AbstractExecutor arg) {
+ // common equality check
+ if (!this.getClass().equals(arg.getClass())) {
+ return false;
+ }
+ if (!this.getMethod().equals(arg.getMethod())) {
+ return false;
+ }
+ if (!this.getTargetClass().equals(arg.getTargetClass())) {
+ return false;
+ }
+ // specific equality check
+ Object lhsp = this.getTargetProperty();
+ Object rhsp = arg.getTargetProperty();
+ if (lhsp == null && rhsp == null) {
+ return true;
+ }
+ if (lhsp != null && rhsp != null) {
+ return lhsp.equals(rhsp);
+ }
+ return false;
+ }
+
+ /**
+ * Tell whether the executor is alive by looking
+ * at the value of the method.
+ *
+ * @return boolean Whether the executor is alive.
+ */
+ public final boolean isAlive() {
+ return (method != null);
+ }
+
+ /**
+ * Specifies if this executor is cacheable and able to be reused for this
+ * class of object it was returned for.
+ *
+ * @return true if can be reused for this class, false if not
+ */
+ public boolean isCacheable() {
+ return method != null;
+ }
+
+ /**
+ * Gets the method to be executed or used as a marker.
+ * @return Method The method used by execute in derived classes.
+ */
+ public final java.lang.reflect.Method getMethod() {
+ return method;
+ }
+
+ /**
+ * Gets the object class targeted by this executor.
+ * @return the target object class
+ */
+ public final Class<?> getTargetClass() {
+ return objectClass;
+ }
+
+ /**
+ * Gets the property targeted by this executor.
+ * @return the target property
+ */
+ public Object getTargetProperty() {
+ return null;
+ }
+
+ /**
+ * Gets the method name used.
+ * @return method name
+ */
+ public final String getMethodName() {
+ return method.getName();
+ }
+
+
+ /**
+ * Checks whether a tryExecute failed or not.
+ * @param exec the value returned by tryExecute
+ * @return true if tryExecute failed, false otherwise
+ */
+ public final boolean tryFailed(Object exec) {
+ return exec == TRY_FAILED;
+ }
+
+ /**
+ * Abstract class that is used to execute an arbitrary 'get' method.
+ */
+ public abstract static class Get extends AbstractExecutor implements JexlPropertyGet {
+ /**
+ * Default and sole constructor.
+ * @param theClass the class this executor applies to
+ * @param theMethod the method held by this executor
+ */
+ protected Get(Class<?> theClass, java.lang.reflect.Method theMethod) {
+ super(theClass, theMethod);
+ }
+
+ /** {@inheritDoc} */
+ public final Object invoke(Object obj) throws Exception {
+ return execute(obj);
+ }
+
+ /** {@inheritDoc} */
+ public final Object tryInvoke(Object obj, Object key) {
+ return tryExecute(obj, key);
+ }
+
+ /**
+ * Gets the property value from an object.
+ *
+ * @param obj The object to get the property from.
+ * @return The property value.
+ * @throws IllegalAccessException Method is inaccessible.
+ * @throws InvocationTargetException Method body throws an exception.
+ */
+ public abstract Object execute(Object obj)
+ throws IllegalAccessException, InvocationTargetException;
+
+ /**
+ * Tries to reuse this executor, checking that it is compatible with
+ * the actual set of arguments.
+ * <p>Compatibility means that:
+ * <code>o</code> must be of the same class as this executor's
+ * target class and
+ * <code>property</code> must be of the same class as this
+ * executor's target property (for list and map based executors) and have the same
+ * value (for other types).</p>
+ * @param obj The object to get the property from.
+ * @param key The property to get from the object.
+ * @return The property value or TRY_FAILED if checking failed.
+ */
+ public Object tryExecute(Object obj, Object key) {
+ return TRY_FAILED;
+ }
+ }
+
+ /**
+ * Abstract class that is used to execute an arbitrary 'set' method.
+ */
+ public abstract static class Set extends AbstractExecutor implements JexlPropertySet {
+ /**
+ * Default and sole constructor.
+ * @param theClass the class this executor applies to
+ * @param theMethod the method held by this executor
+ */
+ protected Set(Class<?> theClass, java.lang.reflect.Method theMethod) {
+ super(theClass, theMethod);
+ }
+
+ /** {@inheritDoc} */
+ public final Object invoke(Object obj, Object arg) throws Exception {
+ return execute(obj, arg);
+ }
+
+ /** {@inheritDoc} */
+ public final Object tryInvoke(Object obj, Object key, Object value) {
+ return tryExecute(obj, key, value);
+ }
+
+ /**
+ * Sets the property value of an object.
+ *
+ * @param obj The object to set the property in.
+ * @param value The value.
+ * @return The return value.
+ * @throws IllegalAccessException Method is inaccessible.
+ * @throws InvocationTargetException Method body throws an exception.
+ */
+ public abstract Object execute(Object obj, Object value)
+ throws IllegalAccessException, InvocationTargetException;
+
+ /**
+ * Tries to reuse this executor, checking that it is compatible with
+ * the actual set of arguments.
+ * <p>Compatibility means that:
+ * <code>o</code> must be of the same class as this executor's
+ * target class,
+ * <code>property</code> must be of the same class as this
+ * executor's target property (for list and map based executors) and have the same
+ * value (for other types)
+ * and that <code>arg</code> must be a valid argument for this
+ * executor underlying method.</p>
+ * @param obj The object to invoke the method from.
+ * @param key The property to set in the object.
+ * @param value The value to use as the property value.
+ * @return The return value or TRY_FAILED if checking failed.
+ */
+ public Object tryExecute(Object obj, Object key, Object value) {
+ return TRY_FAILED;
+ }
+
+ }
+
+
+
+ /**
+ * Abstract class that is used to execute an arbitrary method.
+ */
+ public abstract static class Method extends AbstractExecutor implements JexlMethod {
+ /**
+ * A helper class to pass the method & parameters.
+ */
+ protected static final class Parameter {
+ /** The method. */
+ private final java.lang.reflect.Method method;
+ /** The method key. */
+ private final MethodKey key;
+ /** Creates an instance.
+ * @param m the method
+ * @param k the method key
+ */
+ public Parameter(java.lang.reflect.Method m, MethodKey k) {
+ method = m;
+ key = k;
+ }
+ }
+ /** The method key discovered from the arguments. */
+ protected final MethodKey key;
+ /**
+ * Creates a new instance.
+ * @param c the class this executor applies to
+ * @param km the method and MethodKey to encapsulate.
+ */
+ protected Method(Class<?> c, Parameter km) {
+ super(c, km.method);
+ key = km.key;
+ }
+
+ /** {@inheritDoc} */
+ public final Object invoke(Object obj, Object[] params) throws Exception {
+ return execute(obj, params);
+ }
+
+ /** {@inheritDoc} */
+ public final Object tryInvoke(String name, Object obj, Object[] params) {
+ return tryExecute(name, obj, params);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object getTargetProperty() {
+ return key;
+ }
+
+ /**
+ * Returns the return type of the method invoked.
+ * @return return type
+ */
+ public final Class<?> getReturnType() {
+ return method.getReturnType();
+ }
+
+ /**
+ * Invokes the method to be executed.
+ *
+ * @param obj the object to invoke the method upon
+ * @param args the method arguments
+ * @return the result of the method invocation
+ * @throws IllegalAccessException Method is inaccessible.
+ * @throws InvocationTargetException Method body throws an exception.
+ */
+ public abstract Object execute(Object obj, Object[] args)
+ throws IllegalAccessException, InvocationTargetException;
+
+ /**
+ * Tries to reuse this executor, checking that it is compatible with
+ * the actual set of arguments.
+ * @param obj the object to invoke the method upon
+ * @param name the method name
+ * @param args the method arguments
+ * @return the result of the method invocation or TRY_FAILED if checking failed.
+ */
+ public Object tryExecute(String name, Object obj, Object[] args){
+ return TRY_FAILED;
+ }
+
+ }
+
+}
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/ArrayIterator.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/ArrayIterator.java
new file mode 100644
index 0000000..35f1b35
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/ArrayIterator.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2.internal;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.lang.reflect.Array;
+
+/**
+ * <p>
+ * An Iterator wrapper for an Object[]. This will
+ * allow us to deal with all array like structures
+ * in a consistent manner.
+ * </p>
+ * <p>
+ * WARNING : this class's operations are NOT synchronized.
+ * It is meant to be used in a single thread, newly created
+ * for each use in the #foreach() directive.
+ * If this is used or shared, synchronize in the
+ * next() method.
+ * </p>
+ *
+ * @since 1.0
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class ArrayIterator implements Iterator<Object> {
+ /** The objects to iterate over. */
+ private final Object array;
+ /** The size of the array. */
+ private final int size;
+ /** The current position and size in the array. */
+ private int pos;
+
+ /**
+ * Creates a new iterator instance for the specified array.
+ * @param arr The array for which an iterator is desired.
+ */
+ public ArrayIterator(Object arr) {
+ if (arr == null) {
+ array = null;
+ pos = 0;
+ size = 0;
+ } else if (!arr.getClass().isArray()) {
+ throw new IllegalArgumentException(arr.getClass() + " is not an array");
+ } else {
+ array = arr;
+ pos = 0;
+ size = Array.getLength(array);
+ }
+ }
+
+ /**
+ * Move to next element in the array.
+ *
+ * @return The next object in the array.
+ */
+ public Object next() {
+ if (pos < size) {
+ return Array.get(array, pos++);
+ }
+ // we screwed up...
+ throw new NoSuchElementException("No more elements: " + pos
+ + " / " + size);
+ }
+
+ /**
+ * Check to see if there is another element in the array.
+ *
+ * @return Whether there is another element.
+ */
+ public boolean hasNext() {
+ return (pos < size);
+ }
+
+ /**
+ * No op--merely added to satify the <code>Iterator</code> interface.
+ */
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+}
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/ArrayListWrapper.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/ArrayListWrapper.java
new file mode 100644
index 0000000..ad1ad39
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/ArrayListWrapper.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2.internal;
+
+import java.lang.reflect.Array;
+import java.util.AbstractList;
+
+/**
+ * A class that wraps an array with a List interface.
+ *
+ * @author Chris Schultz <chris@christopherschultz.net$gt;
+ * @version $Revision$ $Date: 2006-04-14 19:40:41 $
+ */
+public class ArrayListWrapper extends AbstractList<Object> {
+ /** the array to wrap. */
+ private final Object array;
+
+ /**
+ * Create the wrapper.
+ * @param anArray {@link #array}
+ */
+ public ArrayListWrapper(Object anArray) {
+ if (!anArray.getClass().isArray()) {
+ throw new IllegalArgumentException(anArray.getClass() + " is not an array");
+ }
+ this.array = anArray;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object get(int index) {
+ return Array.get(array, index);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object set(int index, Object element) {
+ Object old = get(index);
+ Array.set(array, index, element);
+ return old;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int size() {
+ return Array.getLength(array);
+ }
+}
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/BooleanGetExecutor.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/BooleanGetExecutor.java
new file mode 100644
index 0000000..f758193
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/BooleanGetExecutor.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2.internal;
+import java.lang.reflect.InvocationTargetException;
+/**
+ * Specialized executor to get a boolean property from an object.
+ * @since 2.0
+ */
+public final class BooleanGetExecutor extends AbstractExecutor.Get {
+ /** The property. */
+ private final String property;
+ /**
+ * Creates an instance by attempting discovery of the get method.
+ * @param is the introspector
+ * @param clazz the class to introspect
+ * @param key the property to get
+ */
+ public BooleanGetExecutor(Introspector is, Class<?> clazz, String key) {
+ super(clazz, discover(is, clazz, key));
+ property = key;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object getTargetProperty() {
+ return property;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object execute(Object obj)
+ throws IllegalAccessException, InvocationTargetException {
+ return method == null ? null : method.invoke(obj, (Object[]) null);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object tryExecute(Object obj, Object key) {
+ if (obj != null && method != null
+ // ensure method name matches the property name
+ && property.equals(key)
+ && objectClass.equals(obj.getClass())) {
+ try {
+ return method.invoke(obj, (Object[]) null);
+ } catch (InvocationTargetException xinvoke) {
+ return TRY_FAILED; // fail
+ } catch (IllegalAccessException xill) {
+ return TRY_FAILED;// fail
+ }
+ }
+ return TRY_FAILED;
+ }
+
+ /**
+ * Discovers the method for a {@link BooleanGet}.
+ * <p>The method to be found should be named "is{P,p}property and return a boolean.</p>
+ *@param is the introspector
+ *@param clazz the class to find the get method from
+ *@param property the the property name
+ *@return the method if found, null otherwise
+ */
+ static java.lang.reflect.Method discover(Introspector is, final Class<?> clazz, String property) {
+ java.lang.reflect.Method m = PropertyGetExecutor.discoverGet(is, "is", clazz, property);
+ return (m != null && m.getReturnType() == Boolean.TYPE) ? m : null;
+ }
+
+}
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/DuckGetExecutor.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/DuckGetExecutor.java
new file mode 100644
index 0000000..3b1cbf9
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/DuckGetExecutor.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2.internal;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Specialized executor to get a property from an object.
+ * <p>Duck as in duck-typing for an interface like:
+ * <code>
+ * interface Get {
+ * Object get(Object key);
+ * }
+ * </code>
+ * </p>
+ * @since 2.0
+ */
+public final class DuckGetExecutor extends AbstractExecutor.Get {
+ /** The property. */
+ private final Object property;
+
+ /**
+ * Creates an instance by attempting discovery of the get method.
+ * @param is the introspector
+ * @param clazz the class to introspect
+ * @param identifier the property to get
+ */
+ public DuckGetExecutor(Introspector is, Class<?> clazz, Object identifier) {
+ super(clazz, discover(is, clazz, identifier));
+ property = identifier;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object getTargetProperty() {
+ return property;
+ }
+
+ /**
+ * Get the property from the object.
+ * @param obj the object.
+ * @return object.get(property)
+ * @throws IllegalAccessException Method is inaccessible.
+ * @throws InvocationTargetException Method body throws an exception.
+ */
+ @Override
+ public Object execute(Object obj)
+ throws IllegalAccessException, InvocationTargetException {
+ Object[] args = {property};
+ return method == null ? null : method.invoke(obj, args);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object tryExecute(Object obj, Object key) {
+ if (obj != null && method != null
+ // ensure method name matches the property name
+ && property.equals(key)
+ && objectClass.equals(obj.getClass())) {
+ try {
+ Object[] args = {property};
+ return method.invoke(obj, args);
+ } catch (InvocationTargetException xinvoke) {
+ return TRY_FAILED; // fail
+ } catch (IllegalAccessException xill) {
+ return TRY_FAILED;// fail
+ }
+ }
+ return TRY_FAILED;
+ }
+
+ /**
+ * Discovers a method for a {@link GetExecutor.DuckGet}.
+ *@param is the introspector
+ *@param clazz the class to find the get method from
+ *@param identifier the key to use as an argument to the get method
+ *@return the method if found, null otherwise
+ */
+ private static java.lang.reflect.Method discover(Introspector is,
+ final Class<?> clazz, Object identifier) {
+ return is.getMethod(clazz, "get", makeArgs(identifier));
+ }
+}
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/DuckSetExecutor.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/DuckSetExecutor.java
new file mode 100644
index 0000000..3ec9925
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/DuckSetExecutor.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2.internal;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Specialized executor to set a property of an object.
+ * <p>Duck as in duck-typing for an interface like:
+ * <code>
+ * interface Set {
+ * Object set(Object property, Object value);
+ * }
+ * </code>
+ * </p>
+ * @since 2.0
+ */
+public final class DuckSetExecutor extends AbstractExecutor.Set {
+ /** The property. */
+ private final Object property;
+
+ /**
+ * Creates an instance.
+ *@param is the introspector
+ *@param clazz the class to find the set method from
+ *@param key the key to use as 1st argument to the set method
+ *@param value the value to use as 2nd argument to the set method
+ */
+ public DuckSetExecutor(Introspector is, Class<?> clazz, Object key, Object value) {
+ super(clazz, discover(is, clazz, key, value));
+ property = key;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object getTargetProperty() {
+ return property;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object execute(Object obj, Object value)
+ throws IllegalAccessException, InvocationTargetException {
+ Object[] pargs = {property, value};
+ if (method != null) {
+ method.invoke(obj, pargs);
+ }
+ return value;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object tryExecute(Object obj, Object key, Object value) {
+ if (obj != null && method != null
+ // ensure method name matches the property name
+ && property.equals(key)
+ && objectClass.equals(obj.getClass())) {
+ try {
+ Object[] args = {property, value};
+ method.invoke(obj, args);
+ return value;
+ } catch (InvocationTargetException xinvoke) {
+ return TRY_FAILED; // fail
+ } catch (IllegalAccessException xill) {
+ return TRY_FAILED;// fail
+ }
+ }
+ return TRY_FAILED;
+ }
+
+ /**
+ * Discovers the method for a {@link DuckSet}.
+ *@param is the introspector
+ *@param clazz the class to find the set method from
+ *@param key the key to use as 1st argument to the set method
+ *@param value the value to use as 2nd argument to the set method
+ *@return the method if found, null otherwise
+ */
+ private static java.lang.reflect.Method discover(Introspector is,
+ Class<?> clazz, Object key, Object value) {
+ return is.getMethod(clazz, "set", makeArgs(key, value));
+ }
+}
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/EnumerationIterator.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/EnumerationIterator.java
new file mode 100644
index 0000000..91c22a4
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/EnumerationIterator.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2.internal;
+
+
+import java.util.Iterator;
+import java.util.Enumeration;
+
+/**
+ * An Iterator wrapper for an Enumeration.
+ * @since 1.0
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ * @param <T> the type of object this iterator returns
+ */
+public class EnumerationIterator<T> implements Iterator<T> {
+ /**
+ * The enumeration to iterate over.
+ */
+ private final Enumeration<T> enumeration;
+
+ /**
+ * Creates a new iteratorwrapper instance for the specified
+ * Enumeration.
+ *
+ * @param enumer The Enumeration to wrap.
+ */
+ public EnumerationIterator(Enumeration<T> enumer) {
+ enumeration = enumer;
+ }
+
+ /**
+ * Move to next element in the array.
+ *
+ * @return The next object in the array.
+ */
+ public T next() {
+ return enumeration.nextElement();
+ }
+
+ /**
+ * Check to see if there is another element in the array.
+ *
+ * @return Whether there is another element.
+ */
+ public boolean hasNext() {
+ return enumeration.hasMoreElements();
+ }
+
+ /**
+ * Unimplemented. No analogy in Enumeration
+ */
+ public void remove() {
+ // not implemented
+ }
+}
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/Introspector.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/Introspector.java
new file mode 100644
index 0000000..f3ce3f1
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/Introspector.java
@@ -0,0 +1,286 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2.internal;
+
+import java.lang.ref.SoftReference;
+import java.lang.reflect.Method;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+
+import org.apache.commons.jexl2.internal.introspection.IntrospectorBase;
+import org.apache.commons.jexl2.internal.introspection.MethodKey;
+
+import org.apache.commons.logging.Log;
+
+/**
+ * Default introspection services.
+ * <p>Finding methods as well as property getters & setters.</p>
+ * @since 1.0
+ */
+public class Introspector {
+ /** The logger to use for all warnings & errors. */
+ protected final Log rlog;
+ /** The soft reference to the introspector currently in use. */
+ private volatile SoftReference<IntrospectorBase> ref;
+
+ /**
+ * Creates an introspector.
+ * @param log the logger to use for warnings.
+ */
+ protected Introspector(Log log) {
+ rlog = log;
+ ref = new SoftReference<IntrospectorBase>(null);
+ }
+
+ /**
+ * Coerce an Object to an Integer.
+ * @param arg the Object to coerce
+ * @return an Integer if it can be converted, null otherwise
+ */
+ protected Integer toInteger(Object arg) {
+ if (arg == null) {
+ return null;
+ }
+ if (arg instanceof Number) {
+ return Integer.valueOf(((Number) arg).intValue());
+ }
+ try {
+ return Integer.valueOf(arg.toString());
+ } catch (NumberFormatException xnumber) {
+ return null;
+ }
+ }
+
+ /**
+ * Coerce an Object to a String.
+ * @param arg the Object to coerce
+ * @return a String if it can be converted, null otherwise
+ */
+ protected String toString(Object arg) {
+ return arg == null ? null : arg.toString();
+ }
+
+ /**
+ * Gets the current introspector base.
+ * <p>If the reference has been collected, this method will recreate the underlying introspector.</p>
+ * @return the introspector
+ */
+ // CSOFF: DoubleCheckedLocking
+ protected final IntrospectorBase base() {
+ IntrospectorBase intro = ref.get();
+ if (intro == null) {
+ // double checked locking (fixed by Java 5 memory model).
+ synchronized(this) {
+ intro = ref.get();
+ if (intro == null) {
+ intro = new IntrospectorBase(rlog);
+ ref = new SoftReference<IntrospectorBase>(intro);
+ }
+ }
+ }
+ return intro;
+ }
+ // CSON: DoubleCheckedLocking
+
+ /**
+ * Sets the underlying class loader for class solving resolution.
+ * @param loader the loader to use
+ */
+ public void setClassLoader(ClassLoader loader) {
+ base().setLoader(loader);
+ }
+
+
+ /**
+ * Gets the field named by <code>key</code> for the class <code>c</code>.
+ *
+ * @param c Class in which the field search is taking place
+ * @param key Name of the field being searched for
+ * @return the desired field or null if it does not exist or is not accessible
+ * */
+ protected final Field getField(Class<?> c, String key) {
+ return base().getField(c, key);
+ }
+
+ /**
+ * Gets the accessible field names known for a given class.
+ * @param c the class
+ * @return the class field names
+ */
+ public final String[] getFieldNames(Class<?> c) {
+ return base().getFieldNames(c);
+ }
+
+ /**
+ * Gets the method defined by <code>name</code> and
+ * <code>params</code> for the Class <code>c</code>.
+ *
+ * @param c Class in which the method search is taking place
+ * @param name Name of the method being searched for
+ * @param params An array of Objects (not Classes) that describe the
+ * the parameters
+ *
+ * @return The desired Method object.
+ * @throws IllegalArgumentException When the parameters passed in can not be used for introspection.
+ * CSOFF: RedundantThrows
+ */
+ protected final Method getMethod(Class<?> c, String name, Object[] params) throws IllegalArgumentException {
+ return base().getMethod(c, new MethodKey(name, params));
+ }
+
+ /**
+ * Gets the method defined by <code>key</code> and for the Class <code>c</code>.
+ *
+ * @param c Class in which the method search is taking place
+ * @param key MethodKey of the method being searched for
+ *
+ * @return The desired Method object.
+ * @throws IllegalArgumentException When the parameters passed in can not be used for introspection.
+ * CSOFF: RedundantThrows
+ */
+ protected final Method getMethod(Class<?> c, MethodKey key) throws IllegalArgumentException {
+ return base().getMethod(c, key);
+ }
+
+
+ /**
+ * Gets the accessible methods names known for a given class.
+ * @param c the class
+ * @return the class method names
+ */
+ public final String[] getMethodNames(Class<?> c) {
+ return base().getMethodNames(c);
+ }
+
+ /**
+ * Returns a general constructor.
+ * @param ctorHandle the object
+ * @param args contrusctor arguments
+ * @return a {@link java.lang.reflect.Constructor}
+ */
+ public final Constructor<?> getConstructor(Object ctorHandle, Object[] args) {
+ String className = null;
+ Class<?> clazz = null;
+ if (ctorHandle instanceof Class<?>) {
+ clazz = (Class<?>) ctorHandle;
+ className = clazz.getName();
+ } else if (ctorHandle != null) {
+ className = ctorHandle.toString();
+ } else {
+ return null;
+ }
+ return base().getConstructor(clazz, new MethodKey(className, args));
+ }
+
+ /**
+ * Returns a general method.
+ * @param obj the object
+ * @param name the method name
+ * @param args method arguments
+ * @return a {@link AbstractExecutor.Method}.
+ */
+ public final AbstractExecutor.Method getMethodExecutor(Object obj, String name, Object[] args) {
+ AbstractExecutor.Method me = new MethodExecutor(this, obj, name, args);
+ return me.isAlive() ? me : null;
+ }
+
+ /**
+ * Return a property getter.
+ * @param obj the object to base the property from.
+ * @param identifier property name
+ * @return a {@link AbstractExecutor.Get}.
+ */
+ public final AbstractExecutor.Get getGetExecutor(Object obj, Object identifier) {
+ final Class<?> claz = obj.getClass();
+ final String property = toString(identifier);
+ AbstractExecutor.Get executor;
+ // first try for a getFoo() type of property (also getfoo() )
+ if (property != null) {
+ executor = new PropertyGetExecutor(this, claz, property);
+ if (executor.isAlive()) {
+ return executor;
+ }
+ }
+ // look for boolean isFoo()
+ if (property != null) {
+ executor = new BooleanGetExecutor(this, claz, property);
+ if (executor.isAlive()) {
+ return executor;
+ }
+ }
+ // let's see if we are a map...
+ executor = new MapGetExecutor(this, claz, identifier);
+ if (executor.isAlive()) {
+ return executor;
+ }
+ // let's see if we can convert the identifier to an int,
+ // if obj is an array or a list, we can still do something
+ Integer index = toInteger(identifier);
+ if (index != null) {
+ executor = new ListGetExecutor(this, claz, index);
+ if (executor.isAlive()) {
+ return executor;
+ }
+ }
+ // if that didn't work, look for set("foo")
+ executor = new DuckGetExecutor(this, claz, identifier);
+ if (executor.isAlive()) {
+ return executor;
+ }
+ return null;
+ }
+
+ /**
+ * Return a property setter.
+ * @param obj the object to base the property from.
+ * @param identifier property name (or identifier)
+ * @param arg value to set
+ * @return a {@link AbstractExecutor.Set}.
+ */
+ public final AbstractExecutor.Set getSetExecutor(final Object obj, final Object identifier, Object arg) {
+ final Class<?> claz = obj.getClass();
+ final String property = toString(identifier);
+ AbstractExecutor.Set executor;
+ // first try for a setFoo() type of property (also setfoo() )
+ if (property != null) {
+ executor = new PropertySetExecutor(this, claz, property, arg);
+ if (executor.isAlive()) {
+ return executor;
+ }
+ }
+ // let's see if we are a map...
+ executor = new MapSetExecutor(this, claz, identifier, arg);
+ if (executor.isAlive()) {
+ return executor;
+ }
+ // let's see if we can convert the identifier to an int,
+ // if obj is an array or a list, we can still do something
+ Integer index = toInteger(identifier);
+ if (index != null) {
+ executor = new ListSetExecutor(this, claz, index, arg);
+ if (executor.isAlive()) {
+ return executor;
+ }
+ }
+ // if that didn't work, look for set("foo")
+ executor = new DuckSetExecutor(this, claz, property, arg);
+ if (executor.isAlive()) {
+ return executor;
+ }
+ return null;
+ }
+}
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/ListGetExecutor.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/ListGetExecutor.java
new file mode 100644
index 0000000..253f0c6
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/ListGetExecutor.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2.internal;
+import java.util.List;
+import java.lang.reflect.Array;
+/**
+ * Specialized executor to get a property from a List or array.
+ * @since 2.0
+ */
+public final class ListGetExecutor extends AbstractExecutor.Get {
+ /** The java.lang.reflect.Array.get method used as an active marker in ListGet. */
+ private static final java.lang.reflect.Method ARRAY_GET =
+ initMarker(Array.class, "get", Object.class, Integer.TYPE);
+ /** The java.util.obj.get method used as an active marker in ListGet. */
+ private static final java.lang.reflect.Method LIST_GET =
+ initMarker(List.class, "get", Integer.TYPE);
+ /** The property. */
+ private final Integer property;
+
+ /**
+ * Creates an instance checking for the List interface or Array capability.
+ * @param is the introspector
+ * @param clazz the class to introspect
+ * @param key the key to use in obj.get(key)
+ */
+ public ListGetExecutor(Introspector is, Class<?> clazz, Integer key) {
+ super(clazz, discover(clazz));
+ property = key;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object getTargetProperty() {
+ return property;
+ }
+
+ /**
+ * Get the property from the obj or array.
+ * @param obj the List/array.
+ * @return obj.get(key)
+ */
+ @Override
+ public Object execute(final Object obj) {
+ if (method == ARRAY_GET) {
+ return java.lang.reflect.Array.get(obj, property.intValue());
+ } else {
+ return ((List<?>) obj).get(property.intValue());
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object tryExecute(final Object obj, Object key) {
+ if (obj != null && method != null
+ && objectClass.equals(obj.getClass())
+ && key instanceof Integer) {
+ if (method == ARRAY_GET) {
+ return java.lang.reflect.Array.get(obj, ((Integer) key).intValue());
+ } else {
+ return ((List<?>) obj).get(((Integer) key).intValue());
+ }
+ }
+ return TRY_FAILED;
+ }
+
+
+ /**
+ * Finds the method to perform the get on a obj of array.
+ * @param clazz the class to introspect
+ * @return a marker method, obj.get or array.get
+ */
+ static java.lang.reflect.Method discover(Class<?> clazz) {
+ //return discoverList(false, clazz, property);
+ if (clazz.isArray()) {
+ return ARRAY_GET;
+ }
+ if (List.class.isAssignableFrom(clazz)) {
+ return LIST_GET;
+ }
+ return null;
+ }
+}
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/ListSetExecutor.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/ListSetExecutor.java
new file mode 100644
index 0000000..42896fd
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/ListSetExecutor.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2.internal;
+import java.util.List;
+import java.lang.reflect.Array;
+/**
+ * Specialized executor to set a property in a List or array.
+ * @since 2.0
+ */
+public final class ListSetExecutor extends AbstractExecutor.Set {
+ /** The java.lang.reflect.Array.get method used as an active marker in ListGet. */
+ private static final java.lang.reflect.Method ARRAY_SET =
+ initMarker(Array.class, "set", Object.class, Integer.TYPE, Object.class);
+ /** The java.util.obj.set method used as an active marker in ListSet. */
+ private static final java.lang.reflect.Method LIST_SET =
+ initMarker(List.class, "set", Integer.TYPE, Object.class);
+ /** The property. */
+ private final Integer property;
+
+ /**
+ * Creates an instance checking for the List interface or Array capability.
+ * @param is the introspector
+ * @param clazz the class that might implement the map interface
+ * @param key the key to use in obj.set(key,value)
+ * @param value the value to use in obj.set(key,value)
+ */
+ public ListSetExecutor(Introspector is, Class<?> clazz, Integer key, Object value) {
+ super(clazz, discover(clazz));
+ property = key;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object getTargetProperty() {
+ return property;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object execute(final Object obj, Object value) {
+ if (method == ARRAY_SET) {
+ java.lang.reflect.Array.set(obj, property.intValue(), value);
+ } else {
+ @SuppressWarnings("unchecked") // LSE should only be created for array or list types
+ final List<Object> list = (List<Object>) obj;
+ list.set(property.intValue(), value);
+ }
+ return value;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object tryExecute(final Object obj, Object key, Object value) {
+ if (obj != null && method != null
+ && objectClass.equals(obj.getClass())
+ && key instanceof Integer) {
+ if (method == ARRAY_SET) {
+ Array.set(obj, ((Integer) key).intValue(), value);
+ } else {
+ @SuppressWarnings("unchecked") // LSE should only be created for array or list types
+ final List<Object> list = (List<Object>) obj;
+ list.set(((Integer) key).intValue(), value);
+ }
+ return value;
+ }
+ return TRY_FAILED;
+ }
+
+
+ /**
+ * Finds the method to perform 'set' on a obj of array.
+ * @param clazz the class to introspect
+ * @return a marker method, obj.set or array.set
+ */
+ static java.lang.reflect.Method discover(Class<?> clazz) {
+ if (clazz.isArray()) {
+ // we could verify if the call can be performed but it does not change
+ // the fact we would fail...
+ // Class<?> formal = clazz.getComponentType();
+ // Class<?> actual = value == null? Object.class : value.getClass();
+ // if (IntrospectionUtils.isMethodInvocationConvertible(formal, actual, false)) {
+ return ARRAY_SET;
+ // }
+ }
+ if (List.class.isAssignableFrom(clazz)) {
+ return LIST_SET;
+ }
+ return null;
+ }
+}
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/MapGetExecutor.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/MapGetExecutor.java
new file mode 100644
index 0000000..1633bbf
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/MapGetExecutor.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2.internal;
+
+import java.util.Map;
+
+ /**
+ * Specialized executor to get a property from a Map.
+ * @since 2.0
+ */
+public final class MapGetExecutor extends AbstractExecutor.Get {
+ /** The java.util.map.get method used as an active marker in MapGet. */
+ private static final java.lang.reflect.Method MAP_GET =
+ initMarker(Map.class, "get", Object.class);
+ /** The property. */
+ private final Object property;
+
+ /**
+ * Creates an instance checking for the Map interface.
+ * @param is the introspector
+ * @param clazz the class that might implement the map interface
+ * @param key the key to use in map.get(key)
+ */
+ public MapGetExecutor(Introspector is, Class<?> clazz, Object key) {
+ super(clazz, discover(clazz));
+ property = key;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object getTargetProperty() {
+ return property;
+ }
+
+ /**
+ * Get the property from the map.
+ * @param obj the map.
+ * @return map.get(property)
+ */
+ @Override
+ public Object execute(final Object obj) {
+ @SuppressWarnings("unchecked") // ctor only allows Map instances - see discover() method
+ final Map<Object,?> map = (Map<Object, ?>) obj;
+ return map.get(property);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object tryExecute(final Object obj, Object key) {
+ if (obj != null && method != null
+ && objectClass.equals(obj.getClass())
+ && (key == null || property.getClass().equals(key.getClass()))) {
+ @SuppressWarnings("unchecked") // ctor only allows Map instances - see discover() method
+ final Map<Object,?> map = (Map<Object, ?>) obj;
+ return map.get(key);
+ }
+ return TRY_FAILED;
+ }
+
+ /**
+ * Finds the method to perform 'get' on a map.
+ * @param clazz the class to introspect
+ * @return a marker method, map.get
+ */
+ static java.lang.reflect.Method discover(Class<?> clazz) {
+ return (Map.class.isAssignableFrom(clazz))? MAP_GET : null;
+ }
+}
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/MapSetExecutor.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/MapSetExecutor.java
new file mode 100644
index 0000000..7fc001b
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/MapSetExecutor.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2.internal;
+import java.util.Map;
+import java.lang.reflect.InvocationTargetException;
+/**
+ * Specialized executor to set a property in a Map.
+ * @since 2.0
+ */
+public final class MapSetExecutor extends AbstractExecutor.Set {
+ /** The java.util.map.put method used as an active marker in MapSet. */
+ private static final java.lang.reflect.Method MAP_SET = initMarker(Map.class, "put", Object.class, Object.class);
+ /** The property. */
+ private final Object property;
+
+ /**
+ * Creates an instance checking for the Map interface.
+ *@param is the introspector
+ *@param clazz the class that might implement the map interface
+ *@param key the key to use as argument in map.put(key,value)
+ *@param value the value to use as argument in map.put(key,value)
+ */
+ public MapSetExecutor(Introspector is, Class<?> clazz, Object key, Object value) {
+ super(clazz, discover(clazz));
+ property = key;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object getTargetProperty() {
+ return property;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object execute(final Object obj, Object value)
+ throws IllegalAccessException, InvocationTargetException {
+ @SuppressWarnings("unchecked") // ctor only allows Map instances - see discover() method
+ final Map<Object,Object> map = ((Map<Object, Object>) obj);
+ map.put(property, value);
+ return value;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object tryExecute(final Object obj, Object key, Object value) {
+ if (obj != null && method != null
+ && objectClass.equals(obj.getClass())
+ && (key == null || property.getClass().equals(key.getClass()))) {
+ @SuppressWarnings("unchecked") // ctor only allows Map instances - see discover() method
+ final Map<Object,Object> map = ((Map<Object, Object>) obj);
+ map.put(key, value);
+ return value;
+ }
+ return TRY_FAILED;
+ }
+
+ /**
+ * Finds the method to perform 'set' on a map.
+ * @param clazz the class to introspect
+ * @return a marker method, map.get
+ */
+ static java.lang.reflect.Method discover(Class<?> clazz) {
+ return (Map.class.isAssignableFrom(clazz))? MAP_SET : null;
+ }
+}
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/MethodExecutor.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/MethodExecutor.java
new file mode 100644
index 0000000..fb36d00
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/MethodExecutor.java
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2.internal;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import org.apache.commons.jexl2.internal.introspection.MethodKey;
+
+/**
+ * Specialized executor to invoke a method on an object.
+ * @since 2.0
+ */
+public final class MethodExecutor extends AbstractExecutor.Method {
+ /** Whether this method handles varargs. */
+ private final boolean isVarArgs;
+ /**
+ * Creates a new instance.
+ * @param is the introspector used to discover the method
+ * @param obj the object to find the method in
+ * @param name the method name
+ * @param args the method arguments
+ */
+ public MethodExecutor(Introspector is, Object obj, String name, Object[] args) {
+ super(obj.getClass(), discover(is, obj, name, args));
+ isVarArgs = method != null && isVarArgMethod(method);
+ }
+
+ /**
+ * Invokes the method to be executed.
+ * @param o the object to invoke the method upon
+ * @param args the method arguments
+ * @return the result of the method invocation
+ * @throws IllegalAccessException Method is inaccessible.
+ * @throws InvocationTargetException Method body throws an exception.
+ */
+ @Override
+ public Object execute(Object o, Object[] args)
+ throws IllegalAccessException, InvocationTargetException {
+ if (isVarArgs) {
+ Class<?>[] formal = method.getParameterTypes();
+ int index = formal.length - 1;
+ Class<?> type = formal[index].getComponentType();
+ if (args.length >= index) {
+ args = handleVarArg(type, index, args);
+ }
+ }
+ if (method.getDeclaringClass() == ArrayListWrapper.class && o.getClass().isArray()) {
+ return method.invoke(new ArrayListWrapper(o), args);
+ } else {
+ return method.invoke(o, args);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object tryExecute(String name, Object o, Object[] args) {
+ MethodKey tkey = new MethodKey(name, args);
+ // let's assume that invocation will fly if the declaring class is the
+ // same and arguments have the same type
+ if (objectClass.equals(o.getClass()) && tkey.equals(key)) {
+ try {
+ return execute(o, args);
+ } catch (InvocationTargetException xinvoke) {
+ return TRY_FAILED; // fail
+ } catch (IllegalAccessException xill) {
+ return TRY_FAILED;// fail
+ }
+ }
+ return TRY_FAILED;
+ }
+
+
+ /**
+ * Discovers a method for a {@link MethodExecutor}.
+ * <p>
+ * If the object is an array, an attempt will be made to find the
+ * method in a List (see {@link ArrayListWrapper})
+ * </p>
+ * <p>
+ * If the object is a class, an attempt will be made to find the
+ * method as a static method of that class.
+ * </p>
+ * @param is the introspector used to discover the method
+ * @param obj the object to introspect
+ * @param method the name of the method to find
+ * @param args the method arguments
+ * @return a filled up parameter (may contain a null method)
+ */
+ private static Parameter discover(Introspector is,
+ Object obj, String method, Object[] args) {
+ final Class<?> clazz = obj.getClass();
+ final MethodKey key = new MethodKey(method, args);
+ java.lang.reflect.Method m = is.getMethod(clazz, key);
+ if (m == null && clazz.isArray()) {
+ // check for support via our array->list wrapper
+ m = is.getMethod(ArrayListWrapper.class, key);
+ }
+ if (m == null && obj instanceof Class<?>) {
+ m = is.getMethod((Class<?>) obj, key);
+ }
+ return new Parameter(m, key);
+ }
+
+ /**
+ * Reassembles arguments if the method is a vararg method.
+ * @param type The vararg class type (aka component type
+ * of the expected array arg)
+ * @param index The index of the vararg in the method declaration
+ * (This will always be one less than the number of
+ * expected arguments.)
+ * @param actual The actual parameters being passed to this method
+ * @return The actual parameters adjusted for the varargs in order
+ * to fit the method declaration.
+ */
+ protected Object[] handleVarArg(Class<?> type, int index, Object[] actual) {
+ // if no values are being passed into the vararg
+ if (actual.length == index) {
+ // create an empty array of the expected type
+ actual = new Object[]{Array.newInstance(type, 0)};
+ } else if (actual.length == index + 1) {
+ // if one value is being passed into the vararg
+ // make sure the last arg is an array of the expected type
+ if (MethodKey.isInvocationConvertible(type,
+ actual[index].getClass(),
+ false)) {
+ // create a 1-length array to hold and replace the last param
+ Object lastActual = Array.newInstance(type, 1);
+ Array.set(lastActual, 0, actual[index]);
+ actual[index] = lastActual;
+ }
+ } else if (actual.length > index + 1) {
+ // if multiple values are being passed into the vararg
+ // put the last and extra actual in an array of the expected type
+ int size = actual.length - index;
+ Object lastActual = Array.newInstance(type, size);
+ for (int i = 0; i < size; i++) {
+ Array.set(lastActual, i, actual[index + i]);
+ }
+
+ // put all into a new actual array of the appropriate size
+ Object[] newActual = new Object[index + 1];
+ for (int i = 0; i < index; i++) {
+ newActual[i] = actual[i];
+ }
+ newActual[index] = lastActual;
+
+ // replace the old actual array
+ actual = newActual;
+ }
+ return actual;
+ }
+
+ /**
+ * Determines if a method can accept a variable number of arguments.
+ * @param m a the method to check
+ * @return true if method is vararg, false otherwise
+ */
+ private static boolean isVarArgMethod(java.lang.reflect.Method m) {
+ Class<?>[] formal = m.getParameterTypes();
+ if (formal == null || formal.length == 0) {
+ return false;
+ } else {
+ Class<?> last = formal[formal.length - 1];
+ // if the last arg is an array, then
+ // we consider this a varargs method
+ return last.isArray();
+ }
+ }
+}
+
+
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/PropertyGetExecutor.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/PropertyGetExecutor.java
new file mode 100644
index 0000000..fcf7b96
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/PropertyGetExecutor.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2.internal;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Specialized executor to get a property from an object.
+ * @since 2.0
+ */
+public final class PropertyGetExecutor extends AbstractExecutor.Get {
+ /** A static signature for method(). */
+ private static final Object[] EMPTY_PARAMS = {};
+ /** The property. */
+ private final String property;
+
+ /**
+ * Creates an instance by attempting discovery of the get method.
+ * @param is the introspector
+ * @param clazz the class to introspect
+ * @param identifier the property to get
+ */
+ public PropertyGetExecutor(Introspector is, Class<?> clazz, String identifier) {
+ super(clazz, discover(is, clazz, identifier));
+ property = identifier;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object getTargetProperty() {
+ return property;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object execute(Object o)
+ throws IllegalAccessException, InvocationTargetException {
+ return method == null ? null : method.invoke(o, (Object[]) null);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object tryExecute(Object o, Object identifier) {
+ if (o != null && method != null
+ && property.equals(identifier)
+ && objectClass.equals(o.getClass())) {
+ try {
+ return method.invoke(o, (Object[]) null);
+ } catch (InvocationTargetException xinvoke) {
+ return TRY_FAILED; // fail
+ } catch (IllegalAccessException xill) {
+ return TRY_FAILED;// fail
+ }
+ }
+ return TRY_FAILED;
+ }
+
+ /**
+ * Discovers the method for a {@link PropertyGet}.
+ * <p>The method to be found should be named "get{P,p}property.</p>
+ *@param is the introspector
+ *@param clazz the class to find the get method from
+ *@param property the property name to find
+ *@return the method if found, null otherwise
+ */
+ static java.lang.reflect.Method discover(Introspector is,
+ final Class<?> clazz, String property) {
+ return discoverGet(is, "get", clazz, property);
+ }
+
+
+ /**
+ * Base method for boolean & object property get.
+ * @param is the introspector
+ * @param which "is" or "get" for boolean or object
+ * @param clazz The class being examined.
+ * @param property The property being addressed.
+ * @return The {get,is}{p,P}roperty method if one exists, null otherwise.
+ */
+ static java.lang.reflect.Method discoverGet(Introspector is,
+ String which, Class<?> clazz, String property) {
+ // this is gross and linear, but it keeps it straightforward.
+ java.lang.reflect.Method method = null;
+ final int start = which.length(); // "get" or "is" so 3 or 2 for char case switch
+ // start with get<Property>
+ StringBuilder sb = new StringBuilder(which);
+ sb.append(property);
+ // uppercase nth char
+ char c = sb.charAt(start);
+ sb.setCharAt(start, Character.toUpperCase(c));
+ method = is.getMethod(clazz, sb.toString(), EMPTY_PARAMS);
+ //lowercase nth char
+ if (method == null) {
+ sb.setCharAt(start, Character.toLowerCase(c));
+ method = is.getMethod(clazz, sb.toString(), EMPTY_PARAMS);
+ }
+ return method;
+ }
+}
+
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/PropertySetExecutor.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/PropertySetExecutor.java
new file mode 100644
index 0000000..6812edf
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/PropertySetExecutor.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2.internal;
+import java.lang.reflect.InvocationTargetException;
+/**
+ * Specialized executor to set a property in an object.
+ * @since 2.0
+ */
+public final class PropertySetExecutor extends AbstractExecutor.Set {
+ /** Index of the first character of the set{p,P}roperty. */
+ private static final int SET_START_INDEX = 3;
+ /** The property. */
+ private final String property;
+
+ /**
+ * Creates an instance by attempting discovery of the set method.
+ * @param is the introspector
+ * @param clazz the class to introspect
+ * @param identifier the property to set
+ * @param arg the value to set into the property
+ */
+ public PropertySetExecutor(Introspector is, Class<?> clazz, String identifier, Object arg) {
+ super(clazz, discover(is, clazz, identifier, arg));
+ property = identifier;
+
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object getTargetProperty() {
+ return property;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object execute(Object o, Object arg)
+ throws IllegalAccessException, InvocationTargetException {
+ Object[] pargs = {arg};
+ if (method != null) {
+ method.invoke(o, pargs);
+ }
+ return arg;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object tryExecute(Object o, Object identifier, Object arg) {
+ if (o != null && method != null
+ // ensure method name matches the property name
+ && property.equals(identifier)
+ // object class should be same as executor's method declaring class
+ && objectClass.equals(o.getClass())
+ // we are guaranteed the method has one parameter since it is a set(x)
+ && (arg == null || method.getParameterTypes()[0].equals(arg.getClass()))) {
+ try {
+ return execute(o, arg);
+ } catch (InvocationTargetException xinvoke) {
+ return TRY_FAILED; // fail
+ } catch (IllegalAccessException xill) {
+ return TRY_FAILED;// fail
+ }
+ }
+ return TRY_FAILED;
+ }
+
+
+ /**
+ * Discovers the method for a {@link PropertySet}.
+ * <p>The method to be found should be named "set{P,p}property.</p>
+ *@param is the introspector
+ *@param clazz the class to find the get method from
+ *@param property the name of the property to set
+ *@param arg the value to assign to the property
+ *@return the method if found, null otherwise
+ */
+ private static java.lang.reflect.Method discover(Introspector is,
+ final Class<?> clazz, String property, Object arg) {
+ // first, we introspect for the set<identifier> setter method
+ Object[] params = {arg};
+ StringBuilder sb = new StringBuilder("set");
+ sb.append(property);
+ // uppercase nth char
+ char c = sb.charAt(SET_START_INDEX);
+ sb.setCharAt(SET_START_INDEX, Character.toUpperCase(c));
+ java.lang.reflect.Method method = is.getMethod(clazz, sb.toString(), params);
+ // lowercase nth char
+ if (method == null) {
+ sb.setCharAt(SET_START_INDEX, Character.toLowerCase(c));
+ method = is.getMethod(clazz, sb.toString(), params);
+ }
+
+ return method;
+ }
+}
+
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/introspection/ClassMap.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/introspection/ClassMap.java
new file mode 100644
index 0000000..0ba01e5
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/introspection/ClassMap.java
@@ -0,0 +1,347 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2.internal.introspection;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+
+/**
+ * A cache of introspection information for a specific class instance.
+ * Keys objects by an agregation of the method name and the classes
+ * that make up the parameters.
+ * <p>
+ * Originally taken from the Velocity tree so we can be self-sufficient.
+ * </p>
+ * @see MethodKey
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
+ * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
+ * @version $Id$
+ * @since 1.0
+ */
+final class ClassMap {
+ /** cache of methods. */
+ private final MethodCache methodCache;
+ /** cache of fields. */
+ private final Map<String, Field> fieldCache;
+
+ /**
+ * Standard constructor.
+ *
+ * @param aClass the class to deconstruct.
+ * @param log the logger.
+ */
+ ClassMap(Class<?> aClass, Log log) {
+ // eagerly cache methods
+ methodCache = createMethodCache(aClass, log);
+ // eagerly cache public fields
+ fieldCache = createFieldCache(aClass);
+ }
+
+ /**
+ * Find a Field using its name.
+ * <p>The clazz parameter <strong>must</strong> be this ClassMap key.</p>
+ * @param clazz the class to introspect
+ * @param fname the field name
+ * @return A Field object representing the field to invoke or null.
+ */
+ Field findField(final Class<?> clazz, final String fname) {
+ return fieldCache.get(fname);
+ }
+
+ /**
+ * Gets the field names cached by this map.
+ * @return the array of field names
+ */
+ String[] getFieldNames() {
+ return fieldCache.keySet().toArray(new String[fieldCache.size()]);
+ }
+
+ /**
+ * Creates a map of all public fields of a given class.
+ * @param clazz the class to introspect
+ * @return the map of fields (may be the empty map, can not be null)
+ */
+ private static Map<String,Field> createFieldCache(Class<?> clazz) {
+ Field[] fields = clazz.getFields();
+ if (fields.length > 0) {
+ Map<String, Field> cache = new HashMap<String, Field>();
+ for(Field field : fields) {
+ cache.put(field.getName(), field);
+ }
+ return cache;
+ } else {
+ return Collections.emptyMap();
+ }
+ }
+
+
+ /**
+ * Gets the methods names cached by this map.
+ * @return the array of method names
+ */
+ String[] getMethodNames() {
+ return methodCache.names();
+ }
+
+ /**
+ * Find a Method using the method name and parameter objects.
+ *
+ * @param key the method key
+ * @return A Method object representing the method to invoke or null.
+ * @throws MethodKey.AmbiguousException When more than one method is a match for the parameters.
+ */
+ Method findMethod(final MethodKey key)
+ throws MethodKey.AmbiguousException {
+ return methodCache.get(key);
+ }
+
+ /**
+ * Populate the Map of direct hits. These are taken from all the public methods
+ * that our class, its parents and their implemented interfaces provide.
+ * @param classToReflect the class to cache
+ * @param log the Log
+ * @return a newly allocated & filled up cache
+ */
+ private static MethodCache createMethodCache(Class<?> classToReflect, Log log) {
+ //
+ // Build a list of all elements in the class hierarchy. This one is bottom-first (i.e. we start
+ // with the actual declaring class and its interfaces and then move up (superclass etc.) until we
+ // hit java.lang.Object. That is important because it will give us the methods of the declaring class
+ // which might in turn be abstract further up the tree.
+ //
+ // We also ignore all SecurityExceptions that might happen due to SecurityManager restrictions (prominently
+ // hit with Tomcat 5.5).
+ //
+ // We can also omit all that complicated getPublic, getAccessible and upcast logic that the class map had up
+ // until Velocity 1.4. As we always reflect all elements of the tree (that's what we have a cache for), we will
+ // hit the public elements sooner or later because we reflect all the public elements anyway.
+ //
+ // Ah, the miracles of Java for(;;) ...
+ MethodCache cache = new MethodCache();
+ for (;classToReflect != null; classToReflect = classToReflect.getSuperclass()) {
+ if (Modifier.isPublic(classToReflect.getModifiers())) {
+ populateMethodCacheWith(cache, classToReflect, log);
+ }
+ Class<?>[] interfaces = classToReflect.getInterfaces();
+ for (int i = 0; i < interfaces.length; i++) {
+ populateMethodCacheWithInterface(cache, interfaces[i], log);
+ }
+ }
+ return cache;
+ }
+
+ /**
+ * Recurses up interface hierarchy to get all super interfaces.
+ * @param cache the cache to fill
+ * @param iface the interface to populate the cache from
+ * @param log the Log
+ */
+ private static void populateMethodCacheWithInterface(MethodCache cache, Class<?> iface, Log log) {
+ if (Modifier.isPublic(iface.getModifiers())) {
+ populateMethodCacheWith(cache, iface, log);
+ }
+ Class<?>[] supers = iface.getInterfaces();
+ for (int i = 0; i < supers.length; i++) {
+ populateMethodCacheWithInterface(cache, supers[i], log);
+ }
+ }
+
+ /**
+ * Recurses up class hierarchy to get all super classes.
+ * @param cache the cache to fill
+ * @param clazz the class to populate the cache from
+ * @param log the Log
+ */
+ private static void populateMethodCacheWith(MethodCache cache, Class<?> clazz, Log log) {
+ try {
+ Method[] methods = clazz.getDeclaredMethods();
+ for (int i = 0; i < methods.length; i++) {
+ int modifiers = methods[i].getModifiers();
+ if (Modifier.isPublic(modifiers)) {
+ cache.put(methods[i]);
+ }
+ }
+ } catch (SecurityException se) {
+ // Everybody feels better with...
+ if (log.isDebugEnabled()) {
+ log.debug("While accessing methods of " + clazz + ": ", se);
+ }
+ }
+ }
+
+ /**
+ * This is the cache to store and look up the method information.
+ *
+ * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
+ * @version $Id$
+ * <p>
+ * It stores the association between:
+ * - a key made of a method name & an array of argument types.
+ * - a method.
+ * </p>
+ * <p>
+ * Since the invocation of the associated method is dynamic, there is no need (nor way) to differentiate between
+ * foo(int,int) & foo(Integer,Integer) since in practise, only the latter form will be used through a call.
+ * This of course, applies to all 8 primitive types.
+ * </p>
+ * @version $Id$
+ */
+ static final class MethodCache {
+ /**
+ * A method that returns itself used as a marker for cache miss,
+ * allows the underlying cache map to be strongly typed.
+ * @return itself as a method
+ */
+ public static Method cacheMiss() {
+ try {
+ return MethodCache.class.getMethod("cacheMiss");
+ } catch (Exception xio) {
+ // this really cant make an error...
+ return null;
+ }
+ }
+ /** The cache miss marker method. */
+ private static final Method CACHE_MISS = cacheMiss();
+ /** The initial size of the primitive conversion map. */
+ private static final int PRIMITIVE_SIZE = 13;
+ /** The primitive type to class conversion map. */
+ private static final Map<Class<?>, Class<?>> PRIMITIVE_TYPES;
+ static {
+ PRIMITIVE_TYPES = new HashMap<Class<?>, Class<?>>(PRIMITIVE_SIZE);
+ PRIMITIVE_TYPES.put(Boolean.TYPE, Boolean.class);
+ PRIMITIVE_TYPES.put(Byte.TYPE, Byte.class);
+ PRIMITIVE_TYPES.put(Character.TYPE, Character.class);
+ PRIMITIVE_TYPES.put(Double.TYPE, Double.class);
+ PRIMITIVE_TYPES.put(Float.TYPE, Float.class);
+ PRIMITIVE_TYPES.put(Integer.TYPE, Integer.class);
+ PRIMITIVE_TYPES.put(Long.TYPE, Long.class);
+ PRIMITIVE_TYPES.put(Short.TYPE, Short.class);
+ }
+
+ /** Converts a primitive type to its corresponding class.
+ * <p>
+ * If the argument type is primitive then we want to convert our
+ * primitive type signature to the corresponding Object type so
+ * introspection for methods with primitive types will work
+ * correctly.
+ * </p>
+ * @param parm a may-be primitive type class
+ * @return the equivalent object class
+ */
+ static Class<?> primitiveClass(Class<?> parm) {
+ // it is marginally faster to get from the map than call isPrimitive...
+ //if (!parm.isPrimitive()) return parm;
+ Class<?> prim = PRIMITIVE_TYPES.get(parm);
+ return prim == null ? parm : prim;
+ }
+ /**
+ * The method cache.
+ * <p>
+ * Cache of Methods, or CACHE_MISS, keyed by method
+ * name and actual arguments used to find it.
+ * </p>
+ */
+ private final Map<MethodKey, Method> methods = new HashMap<MethodKey, Method>();
+ /**
+ * Map of methods that are searchable according to method parameters to find a match.
+ */
+ private final MethodMap methodMap = new MethodMap();
+
+ /**
+ * Find a Method using the method name and parameter objects.
+ *<p>
+ * Look in the methodMap for an entry. If found,
+ * it'll either be a CACHE_MISS, in which case we
+ * simply give up, or it'll be a Method, in which
+ * case, we return it.
+ *</p>
+ * <p>
+ * If nothing is found, then we must actually go
+ * and introspect the method from the MethodMap.
+ *</p>
+ * @param methodKey the method key
+ * @return A Method object representing the method to invoke or null.
+ * @throws MethodKey.AmbiguousException When more than one method is a match for the parameters.
+ */
+ Method get(final MethodKey methodKey) throws MethodKey.AmbiguousException {
+ synchronized (methodMap) {
+ Method cacheEntry = methods.get(methodKey);
+ // We looked this up before and failed.
+ if (cacheEntry == CACHE_MISS) {
+ return null;
+ }
+
+ if (cacheEntry == null) {
+ try {
+ // That one is expensive...
+ cacheEntry = methodMap.find(methodKey);
+ if (cacheEntry != null) {
+ methods.put(methodKey, cacheEntry);
+ } else {
+ methods.put(methodKey, CACHE_MISS);
+ }
+ } catch (MethodKey.AmbiguousException ae) {
+ // that's a miss :-)
+ methods.put(methodKey, CACHE_MISS);
+ throw ae;
+ }
+ }
+
+ // Yes, this might just be null.
+ return cacheEntry;
+ }
+ }
+
+ /**
+ * Adds a method to the map.
+ * @param method the method to add
+ */
+ void put(Method method) {
+ synchronized (methodMap) {
+ MethodKey methodKey = new MethodKey(method);
+ // We don't overwrite methods. Especially not if we fill the
+ // cache from defined class towards java.lang.Object because
+ // abstract methods in superclasses would else overwrite concrete
+ // classes further down the hierarchy.
+ if (methods.get(methodKey) == null) {
+ methods.put(methodKey, method);
+ methodMap.add(method);
+ }
+ }
+ }
+
+ /**
+ * Gets all the method names from this map.
+ * @return the array of method name
+ */
+ String[] names() {
+ synchronized (methodMap) {
+ return methodMap.names();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/introspection/IntrospectorBase.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/introspection/IntrospectorBase.java
new file mode 100644
index 0000000..2888ce9
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/introspection/IntrospectorBase.java
@@ -0,0 +1,275 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2.internal.introspection;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.List;
+import java.util.LinkedList;
+
+import org.apache.commons.logging.Log;
+
+/**
+ * This basic function of this class is to return a Method object for a
+ * particular class given the name of a method and the parameters to the method
+ * in the form of an Object[]
+ * <p/>
+ * The first time the Introspector sees a class it creates a class method map
+ * for the class in question. Basically the class method map is a Hastable where
+ * Method objects are keyed by a concatenation of the method name and the names
+ * of classes that make up the parameters.
+ *
+ * For example, a method with the following signature:
+ *
+ * public void method(String a, StringBuffer b)
+ *
+ * would be mapped by the key:
+ *
+ * "method" + "java.lang.String" + "java.lang.StringBuffer"
+ *
+ * This mapping is performed for all the methods in a class and stored for
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
+ * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
+ * @author <a href="mailto:paulo.gaspar@krankikom.de">Paulo Gaspar</a>
+ * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
+ * @version $Id$
+ * @since 1.0
+ */
+public class IntrospectorBase {
+ /** the logger. */
+ protected final Log rlog;
+ /**
+ * Holds the method maps for the classes we know about, keyed by Class.
+ */
+ private final Map<Class<?>, ClassMap> classMethodMaps = new HashMap<Class<?>, ClassMap>();
+ /**
+ * The class loader used to solve constructors if needed.
+ */
+ private ClassLoader loader;
+ /**
+ * Holds the map of classes ctors we know about as well as unknown ones.
+ */
+ private final Map<MethodKey, Constructor<?>> constructorsMap = new HashMap<MethodKey, Constructor<?>>();
+ /**
+ * Holds the set of classes we have introspected.
+ */
+ private final Map<String, Class<?>> constructibleClasses = new HashMap<String, Class<?>>();
+
+ /**
+ * Create the introspector.
+ * @param log the logger to use
+ */
+ public IntrospectorBase(Log log) {
+ this.rlog = log;
+ loader = getClass().getClassLoader();
+ }
+
+ /**
+ * Gets the method defined by the <code>MethodKey</code> for the class <code>c</code>.
+ *
+ * @param c Class in which the method search is taking place
+ * @param key Key of the method being searched for
+ * @return The desired Method object.
+ * @throws IllegalArgumentException When the parameters passed in can not be used for introspection.
+ *
+ */
+ //CSOFF: RedundantThrows
+ public Method getMethod(Class<?> c, MethodKey key) {
+ try {
+ ClassMap classMap = getMap(c);
+ return classMap.findMethod(key);
+ } catch (MethodKey.AmbiguousException ae) {
+ // whoops. Ambiguous. Make a nice log message and return null...
+ if (rlog != null) {
+ rlog.error("ambiguous method invocation: "
+ + c.getName() + "."
+ + key.debugString());
+ }
+ }
+ return null;
+
+ }
+ // CSON: RedundantThrows
+
+
+ /**
+ * Gets the field named by <code>key</code> for the class <code>c</code>.
+ *
+ * @param c Class in which the field search is taking place
+ * @param key Name of the field being searched for
+ * @return the desired field or null if it does not exist or is not accessible
+ * */
+ public Field getField(Class<?> c, String key) {
+ ClassMap classMap = getMap(c);
+ return classMap.findField(c, key);
+ }
+
+ /**
+ * Gets the array of accessible field names known for a given class.
+ * @param c the class
+ * @return the class field names
+ */
+ public String[] getFieldNames(Class<?> c) {
+ if (c == null) {
+ return new String[0];
+ }
+ ClassMap classMap = getMap(c);
+ return classMap.getFieldNames();
+ }
+
+ /**
+ * Gets the array of accessible methods names known for a given class.
+ * @param c the class
+ * @return the class method names
+ */
+ public String[] getMethodNames(Class<?> c) {
+ if (c == null) {
+ return new String[0];
+ }
+ ClassMap classMap = getMap(c);
+ return classMap.getMethodNames();
+ }
+
+ /**
+ * A Constructor get cache-miss.
+ */
+ private static class CacheMiss {
+ /** The constructor used as cache-miss. */
+ @SuppressWarnings("unused")
+ public CacheMiss() {}
+ }
+ /** The cache-miss marker for the constructors map. */
+ private static final Constructor<?> CTOR_MISS = CacheMiss.class.getConstructors()[0];
+
+ /**
+ * Sets the class loader used to solve constructors.
+ * <p>Also cleans the constructors cache.</p>
+ * @param cloader the class loader; if null, use this instance class loader
+ */
+ public void setLoader(ClassLoader cloader) {
+ if (cloader == null) {
+ cloader = getClass().getClassLoader();
+ }
+ if (!cloader.equals(loader)) {
+ synchronized(constructorsMap) {
+ loader = cloader;
+ constructorsMap.clear();
+ constructibleClasses.clear();
+ }
+ }
+ }
+
+ /**
+ * Gets the constructor defined by the <code>MethodKey</code>.
+ *
+ * @param key Key of the constructor being searched for
+ * @return The desired Constructor object.
+ * @throws IllegalArgumentException When the parameters passed in can not be used for introspection.
+ */
+ public Constructor<?> getConstructor(final MethodKey key) {
+ return getConstructor(null, key);
+ }
+
+ /**
+ * Gets the constructor defined by the <code>MethodKey</code>.
+ * @param c the class we want to instantiate
+ * @param key Key of the constructor being searched for
+ * @return The desired Constructor object.
+ * @throws IllegalArgumentException When the parameters passed in can not be used for introspection.
+ */
+ //CSOFF: RedundantThrows
+ public Constructor<?> getConstructor(final Class<?> c, final MethodKey key) {
+ try {
+ Constructor<?> ctor = null;
+ synchronized(constructorsMap) {
+ ctor = constructorsMap.get(key);
+ // that's a clear miss
+ if (CTOR_MISS.equals(ctor)) {
+ return null;
+ }
+ // let's introspect...
+ if (ctor == null) {
+ final String cname = key.getMethod();
+ // do we know about this class?
+ Class<?> clazz = constructibleClasses.get(cname);
+ try {
+ // do find the most specific ctor
+ if (clazz == null) {
+ if (c != null && c.getName().equals(key.getMethod())) {
+ clazz = c;
+ } else {
+ clazz = loader.loadClass(cname);
+ }
+ // add it to list of known loaded classes
+ constructibleClasses.put(cname, clazz);
+ }
+ List<Constructor<?>> l = new LinkedList<Constructor<?>>();
+ for(Constructor<?> ictor : clazz.getConstructors()) {
+ l.add(ictor);
+ }
+ // try to find one
+ ctor = key.getMostSpecificConstructor(l);
+ if (ctor != null) {
+ constructorsMap.put(key, ctor);
+ } else {
+ constructorsMap.put(key, CTOR_MISS);
+ }
+ } catch(ClassNotFoundException xnotfound) {
+ if (rlog.isDebugEnabled()) {
+ rlog.debug("could not load class " + cname, xnotfound);
+ }
+ ctor = null;
+ } catch(MethodKey.AmbiguousException xambiguous) {
+ rlog.warn("ambiguous ctor detected for " + cname, xambiguous);
+ ctor = null;
+ }
+ }
+ }
+ return ctor;
+ } catch (MethodKey.AmbiguousException ae) {
+ // whoops. Ambiguous. Make a nice log message and return null...
+ if (rlog != null) {
+ rlog.error("ambiguous constructor invocation: new "
+ + key.debugString());
+ }
+ }
+ return null;
+ }
+ // CSON: RedundantThrows
+
+ /**
+ * Gets the ClassMap for a given class.
+ * @param c the class
+ * @return the class map
+ */
+ private ClassMap getMap(Class<?> c) {
+ synchronized (classMethodMaps) {
+ ClassMap classMap = classMethodMaps.get(c);
+ if (classMap == null) {
+ classMap = new ClassMap(c,rlog);
+ classMethodMaps.put(c, classMap);
+ }
+ return classMap;
+ }
+ }
+}
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/introspection/MethodKey.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/introspection/MethodKey.java
new file mode 100644
index 0000000..e2560c8
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/introspection/MethodKey.java
@@ -0,0 +1,648 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2.internal.introspection;
+import java.util.List;
+import java.util.LinkedList;
+import java.util.Iterator;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+/**
+ * A method key usable by the introspector cache.
+ * <p>
+ * This stores a method (or class) name and parameters.
+ * </p>
+ * <p>
+ * This replaces the original key scheme which used to build the key
+ * by concatenating the method name and parameters class names as one string
+ * with the exception that primitive types were converted to their object class equivalents.
+ * </p>
+ * <p>
+ * The key is still based on the same information, it is just wrapped in an object instead.
+ * Primitive type classes are converted to they object equivalent to make a key;
+ * int foo(int) and int foo(Integer) do generate the same key.
+ * </p>
+ * A key can be constructed either from arguments (array of objects) or from parameters
+ * (array of class).
+ * Roughly 3x faster than string key to access the map & uses less memory.
+ *
+ * For the parameters methods:
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
+ * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
+ * @author Nathan Bubna
+ */
+public final class MethodKey {
+ /** The hash code. */
+ private final int hashCode;
+ /** The method name. */
+ private final String method;
+ /** The parameters. */
+ private final Class<?>[] params;
+ /** A marker for empty parameter list. */
+ private static final Class<?>[] NOARGS = new Class<?>[0];
+ /** The hash code constants. */
+ private static final int HASH = 37;
+
+ /**
+ * Creates a key from a method name and a set of arguments.
+ * @param aMethod the method to generate the key from
+ * @param args the intended method arguments
+ */
+ public MethodKey(String aMethod, Object[] args) {
+ super();
+ // !! keep this in sync with the other ctor (hash code) !!
+ this.method = aMethod;
+ int hash = this.method.hashCode();
+ final int size;
+ // CSOFF: InnerAssignment
+ if (args != null && (size = args.length) > 0) {
+ this.params = new Class<?>[size];
+ for (int p = 0; p < size; ++p) {
+ Object arg = args[p];
+ // null arguments use void as Void.class as marker
+ Class<?> parm = arg == null ? Void.class : arg.getClass();
+ hash = (HASH * hash) + parm.hashCode();
+ this.params[p] = parm;
+ }
+ } else {
+ this.params = NOARGS;
+ }
+ this.hashCode = hash;
+ }
+
+ /**
+ * Creates a key from a method.
+ * @param aMethod the method to generate the key from.
+ */
+ MethodKey(Method aMethod) {
+ this(aMethod.getName(), aMethod.getParameterTypes());
+ }
+
+ /**
+ * Creates a key from a method name and a set of parameters.
+ * @param aMethod the method to generate the key from
+ * @param args the intended method parameters
+ */
+ MethodKey(String aMethod, Class<?>[] args) {
+ super();
+ // !! keep this in sync with the other ctor (hash code) !!
+ this.method = aMethod.intern();
+ int hash = this.method.hashCode();
+ final int size;
+ // CSOFF: InnerAssignment
+ if (args != null && (size = args.length) > 0) {
+ this.params = new Class<?>[size];
+ for (int p = 0; p < size; ++p) {
+ Class<?> parm = ClassMap.MethodCache.primitiveClass(args[p]);
+ hash = (HASH * hash) + parm.hashCode();
+ this.params[p] = parm;
+ }
+ } else {
+ this.params = NOARGS;
+ }
+ this.hashCode = hash;
+ }
+
+ /**
+ * Gets this key's method name.
+ * @return the method name
+ */
+ String getMethod() {
+ return method;
+ }
+
+ /**
+ * Gets this key's method parameter classes.
+ * @return the parameters
+ */
+ Class<?>[] getParameters() {
+ return params;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof MethodKey) {
+ MethodKey key = (MethodKey) obj;
+ return method.equals(key.method) && Arrays.equals(params, key.params);
+ }
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder(method);
+ for (Class<?> c : params) {
+ builder.append(c == Void.class ? "null" : c.getName());
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Outputs a human readable debug representation of this key.
+ * @return method(p0, p1, ...)
+ */
+ public String debugString() {
+ StringBuilder builder = new StringBuilder(method);
+ builder.append('(');
+ for (int i = 0; i < params.length; i++) {
+ if (i > 0) {
+ builder.append(", ");
+ }
+ builder.append(Void.class == params[i] ? "null" : params[i].getName());
+ }
+ builder.append(')');
+ return builder.toString();
+ }
+
+ /**
+ * Gets the most specific method that is applicable to the parameters of this key.
+ * @param methods a list of methods.
+ * @return the most specific method.
+ * @throws MethodKey.AmbiguousException if there is more than one.
+ */
+ public Method getMostSpecificMethod(List<Method> methods) {
+ return METHODS.getMostSpecific(methods, params);
+ }
+
+ /**
+ * Gets the most specific constructor that is applicable to the parameters of this key.
+ * @param methods a list of constructors.
+ * @return the most specific constructor.
+ * @throws MethodKey.AmbiguousException if there is more than one.
+ */
+ public Constructor<?> getMostSpecificConstructor(List<Constructor<?>> methods) {
+ return CONSTRUCTORS.getMostSpecific(methods, params);
+ }
+
+ /**
+ * Determines whether a type represented by a class object is
+ * convertible to another type represented by a class object using a
+ * method invocation conversion, treating object types of primitive
+ * types as if they were primitive types (that is, a Boolean actual
+ * parameter type matches boolean primitive formal type). This behavior
+ * is because this method is used to determine applicable methods for
+ * an actual parameter list, and primitive types are represented by
+ * their object duals in reflective method calls.
+ *
+ * @param formal the formal parameter type to which the actual
+ * parameter type should be convertible
+ * @param actual the actual parameter type.
+ * @param possibleVarArg whether or not we're dealing with the last parameter
+ * in the method declaration
+ * @return true if either formal type is assignable from actual type,
+ * or formal is a primitive type and actual is its corresponding object
+ * type or an object type of a primitive type that can be converted to
+ * the formal type.
+ */
+ public static boolean isInvocationConvertible(Class<?> formal,
+ Class<?> actual,
+ boolean possibleVarArg) {
+ /* if it's a null, it means the arg was null */
+ if (actual == null && !formal.isPrimitive()) {
+ return true;
+ }
+
+ /* Check for identity or widening reference conversion */
+ if (actual != null && formal.isAssignableFrom(actual)) {
+ return true;
+ }
+
+ // CSOFF: NeedBraces
+ /* Check for boxing with widening primitive conversion. Note that
+ * actual parameters are never primitives. */
+ if (formal.isPrimitive()) {
+ if (formal == Boolean.TYPE && actual == Boolean.class)
+ return true;
+ if (formal == Character.TYPE && actual == Character.class)
+ return true;
+ if (formal == Byte.TYPE && actual == Byte.class)
+ return true;
+ if (formal == Short.TYPE
+ && (actual == Short.class || actual == Byte.class))
+ return true;
+ if (formal == Integer.TYPE
+ && (actual == Integer.class || actual == Short.class
+ || actual == Byte.class))
+ return true;
+ if (formal == Long.TYPE
+ && (actual == Long.class || actual == Integer.class
+ || actual == Short.class || actual == Byte.class))
+ return true;
+ if (formal == Float.TYPE
+ && (actual == Float.class || actual == Long.class
+ || actual == Integer.class || actual == Short.class
+ || actual == Byte.class))
+ return true;
+ if (formal == Double.TYPE
+ && (actual == Double.class || actual == Float.class
+ || actual == Long.class || actual == Integer.class
+ || actual == Short.class || actual == Byte.class))
+ return true;
+ }
+ // CSON: NeedBraces
+
+ /* Check for vararg conversion. */
+ if (possibleVarArg && formal.isArray()) {
+ if (actual != null && actual.isArray()) {
+ actual = actual.getComponentType();
+ }
+ return isInvocationConvertible(formal.getComponentType(),
+ actual, false);
+ }
+ return false;
+ }
+
+ /**
+ * Determines whether a type represented by a class object is
+ * convertible to another type represented by a class object using a
+ * method invocation conversion, without matching object and primitive
+ * types. This method is used to determine the more specific type when
+ * comparing signatures of methods.
+ *
+ * @param formal the formal parameter type to which the actual
+ * parameter type should be convertible
+ * @param actual the actual parameter type.
+ * @param possibleVarArg whether or not we're dealing with the last parameter
+ * in the method declaration
+ * @return true if either formal type is assignable from actual type,
+ * or formal and actual are both primitive types and actual can be
+ * subject to widening conversion to formal.
+ */
+ public static boolean isStrictInvocationConvertible(Class<?> formal,
+ Class<?> actual,
+ boolean possibleVarArg) {
+ /* we shouldn't get a null into, but if so */
+ if (actual == null && !formal.isPrimitive()) {
+ return true;
+ }
+
+ /* Check for identity or widening reference conversion */
+ if (formal.isAssignableFrom(actual)) {
+ return true;
+ }
+
+ // CSOFF: NeedBraces
+ /* Check for widening primitive conversion. */
+ if (formal.isPrimitive()) {
+ if (formal == Short.TYPE && (actual == Byte.TYPE))
+ return true;
+ if (formal == Integer.TYPE
+ && (actual == Short.TYPE || actual == Byte.TYPE))
+ return true;
+ if (formal == Long.TYPE
+ && (actual == Integer.TYPE || actual == Short.TYPE
+ || actual == Byte.TYPE))
+ return true;
+ if (formal == Float.TYPE
+ && (actual == Long.TYPE || actual == Integer.TYPE
+ || actual == Short.TYPE || actual == Byte.TYPE))
+ return true;
+ if (formal == Double.TYPE
+ && (actual == Float.TYPE || actual == Long.TYPE
+ || actual == Integer.TYPE || actual == Short.TYPE
+ || actual == Byte.TYPE))
+ return true;
+ }
+ // CSON: NeedBraces
+
+ /* Check for vararg conversion. */
+ if (possibleVarArg && formal.isArray()) {
+ if (actual != null && actual.isArray()) {
+ actual = actual.getComponentType();
+ }
+ return isStrictInvocationConvertible(formal.getComponentType(),
+ actual, false);
+ }
+ return false;
+ }
+
+ /**
+ * whether a method/ctor is more specific than a previously compared one.
+ */
+ private static final int MORE_SPECIFIC = 0;
+ /**
+ * whether a method/ctor is less specific than a previously compared one.
+ */
+ private static final int LESS_SPECIFIC = 1;
+ /**
+ * A method/ctor doesn't match a previously compared one.
+ */
+ private static final int INCOMPARABLE = 2;
+
+ /**
+ * Simple distinguishable exception, used when
+ * we run across ambiguous overloading. Caught
+ * by the introspector.
+ */
+ public static class AmbiguousException extends RuntimeException {
+ /**
+ * Version Id for serializable.
+ */
+ private static final long serialVersionUID = -2314636505414551664L;
+ }
+
+ /**
+ * Utility for parameters matching.
+ * @param <T> Method or Constructor
+ */
+ private abstract static class Parameters<T> {
+ /**
+ * Extract the parameter types from its applicable argument.
+ * @param app a method or constructor
+ * @return the parameters
+ */
+ protected abstract Class<?>[] getParameterTypes(T app);
+
+ // CSOFF: RedundantThrows
+ /**
+ * Gets the most specific method that is applicable to actual argument types.
+ * @param methods a list of methods.
+ * @param classes list of argument types.
+ * @return the most specific method.
+ * @throws MethodKey.AmbiguousException if there is more than one.
+ */
+ private T getMostSpecific(List<T> methods, Class<?>[] classes) {
+ LinkedList<T> applicables = getApplicables(methods, classes);
+
+ if (applicables.isEmpty()) {
+ return null;
+ }
+
+ if (applicables.size() == 1) {
+ return applicables.getFirst();
+ }
+
+ /*
+ * This list will contain the maximally specific methods. Hopefully at
+ * the end of the below loop, the list will contain exactly one method,
+ * (the most specific method) otherwise we have ambiguity.
+ */
+
+ LinkedList<T> maximals = new LinkedList<T>();
+
+ for (Iterator<T> applicable = applicables.iterator();
+ applicable.hasNext();) {
+ T app = applicable.next();
+ Class<?>[] appArgs = getParameterTypes(app);
+
+ boolean lessSpecific = false;
+
+ for (Iterator<T> maximal = maximals.iterator();
+ !lessSpecific && maximal.hasNext();) {
+ T max = maximal.next();
+
+ // CSOFF: MissingSwitchDefault
+ switch (moreSpecific(appArgs, getParameterTypes(max))) {
+ case MORE_SPECIFIC:
+ /*
+ * This method is more specific than the previously
+ * known maximally specific, so remove the old maximum.
+ */
+ maximal.remove();
+ break;
+
+ case LESS_SPECIFIC:
+ /*
+ * This method is less specific than some of the
+ * currently known maximally specific methods, so we
+ * won't add it into the set of maximally specific
+ * methods
+ */
+
+ lessSpecific = true;
+ break;
+ }
+ } // CSON: MissingSwitchDefault
+
+ if (!lessSpecific) {
+ maximals.addLast(app);
+ }
+ }
+
+ if (maximals.size() > 1) {
+ // We have more than one maximally specific method
+ throw new AmbiguousException();
+ }
+
+ return maximals.getFirst();
+ } // CSON: RedundantThrows
+
+ /**
+ * Determines which method signature (represented by a class array) is more
+ * specific. This defines a partial ordering on the method signatures.
+ *
+ * @param c1 first signature to compare
+ * @param c2 second signature to compare
+ * @return MORE_SPECIFIC if c1 is more specific than c2, LESS_SPECIFIC if
+ * c1 is less specific than c2, INCOMPARABLE if they are incomparable.
+ */
+ private int moreSpecific(Class<?>[] c1, Class<?>[] c2) {
+ boolean c1MoreSpecific = false;
+ boolean c2MoreSpecific = false;
+
+ // compare lengths to handle comparisons where the size of the arrays
+ // doesn't match, but the methods are both applicable due to the fact
+ // that one is a varargs method
+ if (c1.length > c2.length) {
+ return MORE_SPECIFIC;
+ }
+ if (c2.length > c1.length) {
+ return LESS_SPECIFIC;
+ }
+
+ // ok, move on and compare those of equal lengths
+ for (int i = 0; i < c1.length; ++i) {
+ if (c1[i] != c2[i]) {
+ boolean last = (i == c1.length - 1);
+ c1MoreSpecific = c1MoreSpecific || isStrictConvertible(c2[i], c1[i], last);
+ c2MoreSpecific = c2MoreSpecific || isStrictConvertible(c1[i], c2[i], last);
+ }
+ }
+
+ if (c1MoreSpecific) {
+ if (c2MoreSpecific) {
+ /*
+ * Incomparable due to cross-assignable arguments (i.e.
+ * foo(String, Object) vs. foo(Object, String))
+ */
+
+ return INCOMPARABLE;
+ }
+
+ return MORE_SPECIFIC;
+ }
+
+ if (c2MoreSpecific) {
+ return LESS_SPECIFIC;
+ }
+
+ /*
+ * Incomparable due to non-related arguments (i.e.
+ * foo(Runnable) vs. foo(Serializable))
+ */
+
+ return INCOMPARABLE;
+ }
+
+ /**
+ * Returns all methods that are applicable to actual argument types.
+ *
+ * @param methods list of all candidate methods
+ * @param classes the actual types of the arguments
+ * @return a list that contains only applicable methods (number of
+ * formal and actual arguments matches, and argument types are assignable
+ * to formal types through a method invocation conversion).
+ */
+ private LinkedList<T> getApplicables(List<T> methods, Class<?>[] classes) {
+ LinkedList<T> list = new LinkedList<T>();
+
+ for (Iterator<T> imethod = methods.iterator(); imethod.hasNext();) {
+ T method = imethod.next();
+ if (isApplicable(method, classes)) {
+ list.add(method);
+ }
+
+ }
+ return list;
+ }
+
+ /**
+ * Returns true if the supplied method is applicable to actual
+ * argument types.
+ *
+ * @param method method that will be called
+ * @param classes arguments to method
+ * @return true if method is applicable to arguments
+ */
+ private boolean isApplicable(T method, Class<?>[] classes) {
+ Class<?>[] methodArgs = getParameterTypes(method);
+
+ if (methodArgs.length > classes.length) {
+ // if there's just one more methodArg than class arg
+ // and the last methodArg is an array, then treat it as a vararg
+ return methodArgs.length == classes.length + 1 && methodArgs[methodArgs.length - 1].isArray();
+ }
+ if (methodArgs.length == classes.length) {
+ // this will properly match when the last methodArg
+ // is an array/varargs and the last class is the type of array
+ // (e.g. String when the method is expecting String...)
+ for (int i = 0; i < classes.length; ++i) {
+ if (!isConvertible(methodArgs[i], classes[i], false)) {
+ // if we're on the last arg and the method expects an array
+ if (i == classes.length - 1 && methodArgs[i].isArray()) {
+ // check to see if the last arg is convertible
+ // to the array's component type
+ return isConvertible(methodArgs[i], classes[i], true);
+ }
+ return false;
+ }
+ }
+ return true;
+ }
+ // more arguments given than the method accepts; check for varargs
+ if (methodArgs.length > 0) {
+ // check that the last methodArg is an array
+ Class<?> lastarg = methodArgs[methodArgs.length - 1];
+ if (!lastarg.isArray()) {
+ return false;
+ }
+
+ // check that they all match up to the last method arg
+ for (int i = 0; i < methodArgs.length - 1; ++i) {
+ if (!isConvertible(methodArgs[i], classes[i], false)) {
+ return false;
+ }
+ }
+
+ // check that all remaining arguments are convertible to the vararg type
+ Class<?> vararg = lastarg.getComponentType();
+ for (int i = methodArgs.length - 1; i < classes.length; ++i) {
+ if (!isConvertible(vararg, classes[i], false)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ // no match
+ return false;
+ }
+
+ /**
+ * @see #isInvocationConvertible(Class, Class, boolean)
+ * @param formal the formal parameter type to which the actual
+ * parameter type should be convertible
+ * @param actual the actual parameter type.
+ * @param possibleVarArg whether or not we're dealing with the last parameter
+ * in the method declaration
+ * @return see isMethodInvocationConvertible.
+ */
+ private boolean isConvertible(Class<?> formal, Class<?> actual,
+ boolean possibleVarArg) {
+ // if we see Void.class, the argument was null
+ return isInvocationConvertible(formal, actual.equals(Void.class)? null : actual, possibleVarArg);
+ }
+
+ /**
+ * @see #isStrictInvocationConvertible(Class, Class, boolean)
+ * @param formal the formal parameter type to which the actual
+ * parameter type should be convertible
+ * @param actual the actual parameter type.
+ * @param possibleVarArg whether or not we're dealing with the last parameter
+ * in the method declaration
+ * @return see isStrictMethodInvocationConvertible.
+ */
+ private boolean isStrictConvertible(Class<?> formal, Class<?> actual,
+ boolean possibleVarArg) {
+ // if we see Void.class, the argument was null
+ return isStrictInvocationConvertible(formal, actual.equals(Void.class)? null : actual, possibleVarArg);
+ }
+
+ }
+
+ /**
+ * The parameter matching service for methods.
+ */
+ private static final Parameters<Method> METHODS = new Parameters<Method>() {
+ @Override
+ protected Class<?>[] getParameterTypes(Method app) {
+ return app.getParameterTypes();
+ }
+ };
+
+
+ /**
+ * The parameter matching service for constructors.
+ */
+ private static final Parameters<Constructor<?>> CONSTRUCTORS = new Parameters<Constructor<?>>() {
+ @Override
+ protected Class<?>[] getParameterTypes(Constructor<?> app) {
+ return app.getParameterTypes();
+ }
+ };
+}
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/introspection/MethodMap.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/introspection/MethodMap.java
new file mode 100644
index 0000000..dfc429e
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/introspection/MethodMap.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2.internal.introspection;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
+ * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
+ * @version $Id$
+ * @since 1.0
+ */
+final class MethodMap {
+ /**
+ * Keep track of all methods with the same name.
+ */
+ private final Map<String, List<Method>> methodByNameMap = new HashMap<String, List<Method>>();
+
+ /**
+ * Add a method to a list of methods by name. For a particular class we are
+ * keeping track of all the methods with the same name.
+ *
+ * @param method the method.
+ */
+ public synchronized void add(Method method) {
+ String methodName = method.getName();
+
+ List<Method> l = methodByNameMap.get(methodName);
+
+ if (l == null) {
+ l = new ArrayList<Method>();
+ methodByNameMap.put(methodName, l);
+ }
+
+ l.add(method);
+ }
+
+ /**
+ * Return a list of methods with the same name.
+ *
+ * @param key the name.
+ * @return List list of methods.
+ */
+ public synchronized List<Method> get(String key) {
+ return methodByNameMap.get(key);
+ }
+
+ /**
+ * Returns the array of method names accessible in this class.
+ * @return the array of names
+ */
+ public synchronized String[] names() {
+ java.util.Set<String> set = methodByNameMap.keySet();
+ return set.toArray(new String[set.size()]);
+ }
+
+ /**
+ * <p>
+ * Find a method. Attempts to find the
+ * most specific applicable method using the
+ * algorithm described in the JLS section
+ * 15.12.2 (with the exception that it can't
+ * distinguish a primitive type argument from
+ * an object type argument, since in reflection
+ * primitive type arguments are represented by
+ * their object counterparts, so for an argument of
+ * type (say) java.lang.Integer, it will not be able
+ * to decide between a method that takes int and a
+ * method that takes java.lang.Integer as a parameter.
+ * </p>
+ *
+ * <p>
+ * This turns out to be a relatively rare case
+ * where this is needed - however, functionality
+ * like this is needed.
+ * </p>
+ *
+ * @param methodName name of method
+ * @param args the actual arguments with which the method is called
+ * @return the most specific applicable method, or null if no
+ * method is applicable.
+ * @throws MethodKey.AmbiguousException if there is more than one maximally
+ * specific applicable method
+ */
+ // CSOFF: RedundantThrows
+ public Method find(String methodName, Object[] args) throws MethodKey.AmbiguousException {
+ return find(new MethodKey(methodName, args));
+ }
+
+ /**
+ * Finds a method by key.
+ * @param methodKey the key
+ * @return the method
+ * @throws MethodKey.AmbiguousException if find is ambiguous
+ */
+ Method find(MethodKey methodKey) throws MethodKey.AmbiguousException {
+ List<Method> methodList = get(methodKey.getMethod());
+ if (methodList == null) {
+ return null;
+ }
+ return methodKey.getMostSpecificMethod(methodList);
+ } // CSON: RedundantThrows
+
+}
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/introspection/package.html b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/introspection/package.html
new file mode 100644
index 0000000..57294df
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/introspection/package.html
@@ -0,0 +1,39 @@
+<html>
+ <!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+ -->
+ <head>
+ <title>Package Documentation for org.apache.commons.jexl2.introspection Package</title>
+ </head>
+ <body bgcolor="white">
+ Provides low-level introspective services.
+ <p>
+ This internal package is not intended for public usage and there is <b>no</b>
+ guarantee that its public classes or methods will remain as is in subsequent
+ versions.
+ </p>
+ <p>
+ The IntrospectorBase, ClassMap, MethodKey, MethodMap form the
+ base of the introspection service. They allow to describe classes and their
+ methods, keeping them in a cache (@see IntrospectorBase) to speed up property
+ getters/setters and method discovery used during expression evaluation.
+ </p>
+ <p>
+ The cache materialized in Introspector creates one entry per class containing a map of all
+ accessible public methods keyed by name and signature.
+ </p>
+ </body>
+</html>
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/package.html b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/package.html
new file mode 100644
index 0000000..5100b92
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/internal/package.html
@@ -0,0 +1,37 @@
+<html>
+ <!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+ -->
+ <head>
+ <title>Package Documentation for org.apache.commons.jexl2 Package</title>
+ </head>
+ <body bgcolor="white">
+ <h2>Provides utilities for introspection services.</h2>
+ <p>
+ This internal package is not intended for public usage and there is <b>no</b>
+ guarantee that its public classes or methods will remain as is in subsequent
+ versions.
+ </p>
+ <p>
+ This set of classes implement the various forms of setters and getters
+ used by Jexl. These are specialized forms for 'pure' properties, discovering
+ methods of the {s,g}etProperty form, for Maps, Lists and Ducks -
+ attempting to discover a 'get' or 'set' method, making an object walk and
+ quack.
+ </p>
+
+ </body>
+</html>
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/introspection/JexlMethod.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/introspection/JexlMethod.java
new file mode 100644
index 0000000..ddcfb97
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/introspection/JexlMethod.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2.introspection;
+
+/**
+ * Interface used for regular method invocation.
+ * Ex.
+ * <code>
+ * ${foo.bar()}
+ * </code>
+ *
+ * @since 1.0
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public interface JexlMethod {
+ /**
+ * Invocation method, called when the method invocation should be performed
+ * and a value returned.
+
+ * @param obj the object
+ * @param params method parameters.
+ * @return the result
+ * @throws Exception on any error.
+ */
+ Object invoke(Object obj, Object[] params) throws Exception;
+
+ /**
+ * Attempts to reuse this JexlMethod, checking that it is compatible with
+ * the actual set of arguments.
+ * Related to isCacheable since this method is often used with cached JexlMethod instances.
+ * @param obj the object to invoke the method upon
+ * @param name the method name
+ * @param params the method arguments
+ * @return the result of the method invocation that should be checked by tryFailed to determine if it succeeded
+ * or failed.
+ */
+ Object tryInvoke(String name, Object obj, Object[] params);
+
+ /**
+ * Checks whether a tryInvoke failed or not.
+ * @param rval the value returned by tryInvoke
+ * @return true if tryInvoke failed, false otherwise
+ */
+ boolean tryFailed(Object rval);
+
+ /**
+ * Specifies if this JexlMethod is cacheable and able to be reused for this
+ * class of object it was returned for.
+ *
+ * @return true if can be reused for this class, false if not
+ */
+ boolean isCacheable();
+
+ /**
+ * returns the return type of the method invoked.
+ * @return return type
+ */
+ Class<?> getReturnType();
+}
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/introspection/JexlPropertyGet.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/introspection/JexlPropertyGet.java
new file mode 100644
index 0000000..2130c0f
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/introspection/JexlPropertyGet.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2.introspection;
+
+/**
+ * Interface for getting values that appear to be properties.
+ * Ex.
+ * <code>
+ * ${foo.bar}
+ * </code>
+ * @since 1.0
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public interface JexlPropertyGet {
+ /**
+ * Method used to get the property value of an object.
+ *
+ * @param obj the object to get the property value from.
+ * @return the property value.
+ * @throws Exception on any error.
+ */
+ Object invoke(Object obj) throws Exception;
+
+ /**
+ * Attempts to reuse this JexlPropertyGet, checking that it is compatible with
+ * the actual set of arguments.
+ * @param obj the object to invoke the property get upon
+ * @param key the property key to get
+ * @return the result of the method invocation that should be checked by tryFailed to determine if it succeeded
+ * or failed.
+ */
+ Object tryInvoke(Object obj, Object key);
+
+ /**
+ * Checks whether a tryInvoke failed or not.
+ * @param rval the value returned by tryInvoke
+ * @return true if tryInvoke failed, false otherwise
+ */
+ boolean tryFailed(Object rval);
+
+ /**
+ * Specifies if this JexlPropertyGet is cacheable and able to be reused for
+ * this class of object it was returned for.
+ *
+ * @return true if can be reused for this class, false if not
+ */
+ boolean isCacheable();
+}
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/introspection/JexlPropertySet.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/introspection/JexlPropertySet.java
new file mode 100644
index 0000000..c49f5f1
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/introspection/JexlPropertySet.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2.introspection;
+
+/**
+ * Interface used for setting values that appear to be properties.
+ * Ex.
+ * <code>
+ * ${foo.bar = "hello"}
+ * </code>
+ * @since 1.0
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public interface JexlPropertySet {
+ /**
+ * Method used to set the property value of an object.
+ *
+ * @param obj Object on which the property setter will be called with the value
+ * @param arg value to be set
+ * @return the value returned from the set operation (impl specific)
+ * @throws Exception on any error.
+ */
+ Object invoke(Object obj, Object arg) throws Exception;
+
+ /**
+ * Attempts to reuse this JexlPropertySet, checking that it is compatible with
+ * the actual set of arguments.
+ * @param obj the object to invoke the the get upon
+ * @param key the property key to get
+ * @param value the property value to set
+ * @return the result of the method invocation that should be checked by tryFailed to determine if it succeeded
+ * or failed.
+ */
+ Object tryInvoke(Object obj, Object key, Object value);
+
+ /**
+ * Checks whether a tryInvoke failed or not.
+ * @param rval the value returned by tryInvoke
+ * @return true if tryInvoke failed, false otherwise
+ */
+ boolean tryFailed(Object rval);
+
+ /**
+ * Specifies if this JexlPropertySet is cacheable and able to be reused for
+ * this class of object it was returned for.
+ *
+ * @return true if can be reused for this class, false if not
+ */
+ boolean isCacheable();
+}
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/introspection/Uberspect.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/introspection/Uberspect.java
new file mode 100644
index 0000000..5230fc8
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/introspection/Uberspect.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2.introspection;
+
+import java.util.Iterator;
+import java.lang.reflect.Constructor;
+import org.apache.commons.jexl2.JexlInfo;
+
+/**
+ * 'Federated' introspection/reflection interface to allow the introspection
+ * behavior in JEXL to be customized.
+ *
+ * @since 1.0
+ * @author <a href="mailto:geirm@apache.org">Geir Magusson Jr.</a>
+ * @version $Id$
+ */
+public interface Uberspect {
+ /** Sets the class loader to use when getting a constructor with
+ * a class name parameter.
+ * @param loader the class loader
+ */
+ void setClassLoader(ClassLoader loader);
+
+ /**
+ * Returns a class constructor.
+ * @param ctorHandle a class or class name
+ * @param args constructor arguments
+ * @param info contextual information
+ * @return a {@link Constructor}
+ */
+ Constructor<?> getConstructor(Object ctorHandle, Object[] args, JexlInfo info);
+ /**
+ * Returns a JexlMethod.
+ * @param obj the object
+ * @param method the method name
+ * @param args method arguments
+ * @param info contextual information
+ * @return a {@link JexlMethod}
+ */
+ JexlMethod getMethod(Object obj, String method, Object[] args, JexlInfo info);
+
+ /**
+ * Property getter.
+ * <p>Returns JexlPropertyGet appropos for ${bar.woogie}.
+ * @param obj the object to get the property from
+ * @param identifier property name
+ * @param info contextual information
+ * @return a {@link JexlPropertyGet}
+ */
+ JexlPropertyGet getPropertyGet(Object obj, Object identifier, JexlInfo info);
+
+ /**
+ * Property setter.
+ * <p>returns JelPropertySet appropos for ${foo.bar = "geir"}</p>.
+ * @param obj the object to get the property from.
+ * @param identifier property name
+ * @param arg value to set
+ * @param info contextual information
+ * @return a {@link JexlPropertySet}.
+ */
+ JexlPropertySet getPropertySet(Object obj, Object identifier, Object arg, JexlInfo info);
+
+ /**
+ * Gets an iterator from an object.
+ * @param obj to get the iterator for
+ * @param info contextual information
+ * @return an iterator over obj
+ */
+ Iterator<?> getIterator(Object obj, JexlInfo info);
+
+}
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/introspection/UberspectImpl.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/introspection/UberspectImpl.java
new file mode 100644
index 0000000..e975727
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/introspection/UberspectImpl.java
@@ -0,0 +1,258 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2.introspection;
+
+import org.apache.commons.jexl2.internal.Introspector;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Enumeration;
+import java.util.Iterator;
+
+import java.util.Map;
+import org.apache.commons.jexl2.JexlInfo;
+import org.apache.commons.jexl2.JexlException;
+import org.apache.commons.jexl2.internal.AbstractExecutor;
+import org.apache.commons.jexl2.internal.ArrayIterator;
+import org.apache.commons.jexl2.internal.EnumerationIterator;
+import org.apache.commons.jexl2.internal.introspection.MethodKey;
+import org.apache.commons.logging.Log;
+
+/**
+ * Implementation of Uberspect to provide the default introspective
+ * functionality of JEXL.
+ * <p>This is the class to derive to customize introspection.</p>
+ *
+ * @since 1.0
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
+ * @version $Id$
+ */
+public class UberspectImpl extends Introspector implements Uberspect {
+ /**
+ * Publicly exposed special failure object returned by tryInvoke.
+ */
+ public static final Object TRY_FAILED = AbstractExecutor.TRY_FAILED;
+
+ /**
+ * Creates a new UberspectImpl.
+ * @param runtimeLogger the logger used for all logging needs
+ */
+ public UberspectImpl(Log runtimeLogger) {
+ super(runtimeLogger);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @SuppressWarnings("unchecked")
+ public Iterator<?> getIterator(Object obj, JexlInfo info) {
+ if (obj instanceof Iterator<?>) {
+ return ((Iterator<?>) obj);
+ }
+ if (obj.getClass().isArray()) {
+ return new ArrayIterator(obj);
+ }
+ if (obj instanceof Map<?,?>) {
+ return ((Map<?,?>) obj).values().iterator();
+ }
+ if (obj instanceof Enumeration<?>) {
+ return new EnumerationIterator<Object>((Enumeration<Object>) obj);
+ }
+ if (obj instanceof Iterable<?>) {
+ return ((Iterable<?>) obj).iterator();
+ }
+ try {
+ // look for an iterator() method to support the JDK5 Iterable
+ // interface or any user tools/DTOs that want to work in
+ // foreach without implementing the Collection interface
+ AbstractExecutor.Method it = getMethodExecutor(obj, "iterator", null);
+ if (it != null && Iterator.class.isAssignableFrom(it.getReturnType())) {
+ return (Iterator<Object>) it.execute(obj, null);
+ }
+ } catch(Exception xany) {
+ throw new JexlException(info, "unable to generate iterator()", xany);
+ }
+ return null;
+ }
+
+ /**
+ * Returns a class field.
+ * @param obj the object
+ * @param name the field name
+ * @param info debug info
+ * @return a {@link Field}.
+ */
+ public Field getField(Object obj, String name, JexlInfo info) {
+ final Class<?> clazz = obj instanceof Class<?>? (Class<?>) obj : obj.getClass();
+ return getField(clazz, name);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Constructor<?> getConstructor(Object ctorHandle, Object[] args, JexlInfo info) {
+ return getConstructor(ctorHandle, args);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public JexlMethod getMethod(Object obj, String method, Object[] args, JexlInfo info) {
+ return getMethodExecutor(obj, method, args);
+ }
+
+ /**
+ * A JexlPropertyGet for public fields.
+ */
+ public static final class FieldPropertyGet implements JexlPropertyGet {
+ /**
+ * The public field.
+ */
+ private final Field field;
+
+ /**
+ * Creates a new instance of FieldPropertyGet.
+ * @param theField the class public field
+ */
+ public FieldPropertyGet(Field theField) {
+ field = theField;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object invoke(Object obj) throws Exception {
+ return field.get(obj);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object tryInvoke(Object obj, Object key) {
+ if (obj.getClass().equals(field.getDeclaringClass()) && key.equals(field.getName())) {
+ try {
+ return field.get(obj);
+ } catch (IllegalAccessException xill) {
+ return TRY_FAILED;
+ }
+ }
+ return TRY_FAILED;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean tryFailed(Object rval) {
+ return rval == TRY_FAILED;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isCacheable() {
+ return true;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public JexlPropertyGet getPropertyGet(Object obj, Object identifier, JexlInfo info) {
+ JexlPropertyGet get = getGetExecutor(obj, identifier);
+ if (get == null && obj != null && identifier != null) {
+ Field field = getField(obj, identifier.toString(), info);
+ if (field != null) {
+ return new FieldPropertyGet(field);
+ }
+ }
+ return get;
+ }
+
+ /**
+ * A JexlPropertySet for public fields.
+ */
+ public static final class FieldPropertySet implements JexlPropertySet {
+ /**
+ * The public field.
+ */
+ private final Field field;
+
+ /**
+ * Creates a new instance of FieldPropertySet.
+ * @param theField the class public field
+ */
+ public FieldPropertySet(Field theField) {
+ field = theField;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object invoke(Object obj, Object arg) throws Exception {
+ field.set(obj, arg);
+ return arg;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object tryInvoke(Object obj, Object key, Object value) {
+ if (obj.getClass().equals(field.getDeclaringClass())
+ && key.equals(field.getName())
+ && (value == null || MethodKey.isInvocationConvertible(field.getType(), value.getClass(), false))) {
+ try {
+ field.set(obj, value);
+ return value;
+ } catch (IllegalAccessException xill) {
+ return TRY_FAILED;
+ }
+ }
+ return TRY_FAILED;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean tryFailed(Object rval) {
+ return rval == TRY_FAILED;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isCacheable() {
+ return true;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public JexlPropertySet getPropertySet(final Object obj, final Object identifier, Object arg, JexlInfo info) {
+ JexlPropertySet set = getSetExecutor(obj, identifier, arg);
+ if (set == null && obj != null && identifier != null) {
+ Field field = getField(obj, identifier.toString(), info);
+ if (field != null
+ && !Modifier.isFinal(field.getModifiers())
+ && (arg == null || MethodKey.isInvocationConvertible(field.getType(), arg.getClass(), false))) {
+ return new FieldPropertySet(field);
+ }
+ }
+ return set;
+ }
+}
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/introspection/package.html b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/introspection/package.html
new file mode 100644
index 0000000..e8eba16
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/introspection/package.html
@@ -0,0 +1,34 @@
+<html>
+ <!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+ -->
+ <head>
+ <title>Package Documentation for org.apache.commons.jexl2.introspection Package</title>
+ </head>
+ <body bgcolor="white">
+ <h2>Provides high-level introspective services.</h2>
+ <p>
+ The Uberspect, JexlMethod, JexlPropertyGet and JexlPropertySet interfaces
+ form the exposed face of introspective services.
+ </p>
+ <p>
+ The Uberspectimpl is the concrete class implementing the Uberspect interface.
+ Deriving from this class is the preferred way of augmenting Jexl introspective
+ capabilities when special needs to be fullfilled or when default behaviors
+ need to be modified.
+ </p>
+ </body>
+</html>
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/package.html b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/package.html
new file mode 100644
index 0000000..03aa31d
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/package.html
@@ -0,0 +1,276 @@
+<html>
+ <!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+ -->
+ <head>
+ <title>Package Documentation for org.apache.commons.jexl2 Package</title>
+ </head>
+ <body bgcolor="white">
+ Provides a framework for evaluating JEXL expressions.
+ <br/><br/>
+ <ul>
+ <li><a href="#intro">Introduction</a></li>
+ <li><a href="#example">Brief Example</a></li>
+ <li><a href="#usage">Using JEXL</a></li>
+ <li><a href="#configuration">Configuring JEXL</a></li>
+ <li><a href="#customization">Customizing JEXL</a></li>
+ </ul>
+
+ <h2><a name="intro">Introduction</a></h2>
+ <p>
+ JEXL is a library intended to facilitate the implementation of dynamic and scripting features in applications
+ and frameworks.
+ </p>
+
+ <h2><a name="example">A Brief Example</a></h2>
+ <p>
+ When evaluating expressions, JEXL merges an
+ {@link org.apache.commons.jexl2.Expression}
+ with a
+ {@link org.apache.commons.jexl2.JexlContext}.
+ An Expression is created using
+ {@link org.apache.commons.jexl2.JexlEngine#createExpression(java.lang.String)},
+ passing a String containing valid JEXL syntax. A simple JexlContext can be created using
+ a {@link org.apache.commons.jexl2.MapContext} instance;
+ a map of variables that will be internally wrapped can be optionally provided through its constructor.
+ The following example, takes a variable named foo, and
+ invokes the bar() method on the property innerFoo:
+ </p>
+ <pre>
+ // Create a JexlEngine (could reuse one instead)
+ JexlEngine jexl = new JexlEngine();
+ // Create an expression object
+ String jexlExp = "foo.innerFoo.bar()";
+ Expression e = jexl.createExpression( jexlExp );
+
+ // Create a context and add data
+ JexlContext jc = new MapContext();
+ jc.set("foo", new Foo() );
+
+ // Now evaluate the expression, getting the result
+ Object o = e.evaluate(jc);
+ </pre>
+
+
+ <h2><a name="usage">Using JEXL</a></h2>
+ The API is composed of three levels addressing different functional needs:
+ <ul>
+ <li>Dynamic invocation of setters, getters, methods and constructors</li>
+ <li>Script expressions known as JEXL expressions</li>
+ <li>JSP/JSF like expression known as UnifiedJEXL expressions</li>
+ </ul>
+
+ <h3><a name="usage_note">Important note</a></h3>
+ The only public packages you should use are:
+ <ul>
+ <li>org.apache.commons.jexl2</li>
+ <li>org.apache.commons.jexl2.introspection</li>
+ </ul>
+ The following packages follow a "use at your own maintenance cost" policy.
+ Their classes and methods are not guaranteed to remain compatible in subsequent versions.
+ If you think you need to use some of their features, it might be a good idea to check with
+ the community through the mailing list first.
+ <ul>
+ <li>org.apache.commons.jexl2.parser</li>
+ <li>org.apache.commons.jexl2.scripting</li>
+ <li>org.apache.commons.jexl2.internal</li>
+ <li>org.apache.commons.jexl2.internal.introspection</li>
+ </ul>
+
+ <h3><a name="usage_api">Dynamic invocation</a></h3>
+ <p>
+ These functionalities are close to the core level utilities found in
+ <a href="http://commons.apache.org/beanutils/">BeanUtils</a>.
+ For basic dynamic property manipulations and method invocation, you can use the following
+ set of methods:
+ </p>
+ <ul>
+ <li>{@link org.apache.commons.jexl2.JexlEngine#newInstance}</li>
+ <li>{@link org.apache.commons.jexl2.JexlEngine#setProperty}</li>
+ <li>{@link org.apache.commons.jexl2.JexlEngine#getProperty}</li>
+ <li>{@link org.apache.commons.jexl2.JexlEngine#invokeMethod}</li>
+ </ul>
+ The following example illustrate their usage:
+ <pre>
+ // test outer class
+ public static class Froboz {
+ int value;
+ public Froboz(int v) { value = v; }
+ public void setValue(int v) { value = v; }
+ public int getValue() { return value; }
+ }
+ // test inner class
+ public static class Quux {
+ String str;
+ Froboz froboz;
+ public Quux(String str, int fro) {
+ this.str = str;
+ froboz = new Froboz(fro);
+ }
+ public Froboz getFroboz() { return froboz; }
+ public void setFroboz(Froboz froboz) { this.froboz = froboz; }
+ public String getStr() { return str; }
+ public void setStr(String str) { this.str = str; }
+ }
+ // test API
+ JexlEngine jexl = nex JexlEngine();
+ Quux quux = jexl.newInstance(Quux.class, "xuuq", 100);
+ jexl.setProperty(quux, "froboz.value", Integer.valueOf(100));
+ Object o = jexl.getProperty(quux, "froboz.value");
+ assertEquals("Result is not 100", new Integer(100), o);
+ jexl.setProperty(quux, "['froboz'].value", Integer.valueOf(1000));
+ o = jexl.getProperty(quux, "['froboz']['value']");
+ assertEquals("Result is not 1000", new Integer(1000), o);
+ </pre>
+
+ <h3><a name="usage_jexl">JEXL script expression</a></h3>
+ <p>
+ If your needs require simple expression evaluation capabilities, the core JEXL features
+ will most likely fit.
+ The main methods are:
+ </p>
+ <ul>
+ <li>{@link org.apache.commons.jexl2.JexlEngine#createExpression}</li>
+ <li>{@link org.apache.commons.jexl2.JexlEngine#createScript}</li>
+ <li>{@link org.apache.commons.jexl2.Expression#evaluate}</li>
+ </ul>
+ The following example illustrates their usage:
+ <pre>
+ JexlEngine jexl = nex JexlEngine();
+
+ JexlContext jc = new MapContext();
+ jc.set("quuxClass", quux.class);
+
+ Expression create = jexl.createExpression("quux = new(quuxClass, 'xuuq', 100)");
+ Expression assign = jexl.createExpression("quux.froboz.value = 10");
+ Expression check = jexl.createExpression("quux[\"froboz\"].value");
+ Quux quux = (Quux) create.evaluate(jc);
+ Object o = assign.evaluate(jc);
+ assertEquals("Result is not 10", new Integer(10), o);
+ o = check.evaluate(jc);
+ assertEquals("Result is not 10", new Integer(10), o);
+ </pre>
+
+ <h3><a name="usage_ujexl">UnifiedJEXL script expressions</a></h3>
+ <p>
+ If you are looking for JSP-EL like and basic templating features, you can
+ use UnifiedJEXL.
+ </p>
+ The main methods are:
+ <ul>
+ <li>{@link org.apache.commons.jexl2.UnifiedJEXL#parse}</li>
+ <li>{@link org.apache.commons.jexl2.UnifiedJEXL.Expression#evaluate}</li>
+ <li>{@link org.apache.commons.jexl2.UnifiedJEXL.Expression#prepare}</li>
+ </ul>
+ The following example illustrates their usage:
+ <pre>
+ JexlEngine jexl = new JexlEngine();
+ UnifiedJEXL ujexl = new UnifiedJEXL(jexl);
+ UnifiedJEXL.Expression expr = ujexl.parse("Hello ${user}");
+ String hello = expr.evaluate(context, expr).toString();
+ </pre>
+
+ <h2><a name="configuration">JEXL Configuration</a></h2>
+ <p>
+ The JexlEngine can be configured through a few parameters that will drive how it reacts
+ in case of errors. These configuration methods are best called at JEXL engine initialization time; it
+ is recommended to derive from JexlEngine to call those in a constructor.
+ </p>
+ <p>
+ {@link org.apache.commons.jexl2.JexlEngine#setLenient} configures when JEXL considers 'null' as an error or not in various situations;
+ when facing an unreferenceable variable, using null as an argument to an arithmetic operator or failing to call
+ a method or constructor. The lenient mode is close to JEXL-1.1 behavior.
+ </p>
+ <p>
+ {@link org.apache.commons.jexl2.JexlEngine#setSilent} configures how JEXL reacts to errors; if silent, the engine will not throw exceptions
+ but will warn through loggers and return null in case of errors. Note that when non-silent, JEXL throws
+ JexlException which are unchecked exception.
+ </p>
+ <p>
+ {@link org.apache.commons.jexl2.JexlEngine#setDebug} makes stacktraces carried by JExlException more meaningfull; in particular, these
+ traces will carry the exact caller location the Expression was created from.
+ </p>
+ <p>
+ {@link org.apache.commons.jexl2.JexlEngine#setClassLoader} indicates to a JexlEngine which class loader to use to solve a class name; this affects
+ how JexlEngine.newInstance and the 'new' script method operates. This is mostly usefull in cases where
+ you rely on JEXL to dynamically load and call plugins for your application.
+ </p>
+ <p>
+ JexlEngine and UnifiedJEXL expression caches can be configured as well. If you intend to use JEXL
+ repeatedly in your application, these are worth configuring since expression parsing is quite heavy.
+ Note that all caches created by JEXL are held through SoftReference; under high memory pressure, the GC will be able
+ to reclaim those caches and JEXL will rebuild them if needed. By default, a JexlEngine does not create a cache
+ whilst UnifiedJEXL does.
+ </p>
+ <p>Both JexlEngine and UnifiedJEXL are thread-safe; the same instance can be shared between different
+ threads and proper synchronization is enforced in critical areas.</p>
+ <p>{@link org.apache.commons.jexl2.JexlEngine#setCache} will set how many expressions can be simultaneously cached by the
+ JEXL engine. UnifiedJEXL allows to define the cache size through its constructor.</p>
+ <p>
+ {@link org.apache.commons.jexl2.JexlEngine#setFunctions} extends JEXL scripting by registering functions in
+ namespaces.
+ </p>
+ This can be used as in:
+ <pre><code>
+ public static MyMath {
+ public double cos(double x) {
+ return Math.cos(x);
+ }
+ }
+ Map<String, Object> funcs = new HashMap<String, Object>();
+ funcs.put("math", new MyMath());
+ JexlEngine jexl = new JexlEngine();
+ jexl.setFunctions(funcs);
+
+ JexlContext jc = new MapContext();
+ jc.set("pi", Math.PI);
+
+ e = JEXL.createExpression("math:cos(pi)");
+ o = e.evaluate(jc);
+ assertEquals(Double.valueOf(-1),o);
+ </code></pre>
+ If the <i>namespace</i> is a Class and that class declares a constructor that takes a JexlContext (or
+ a class extending JexlContext), one <i>namespace</i> instance is created on first usage in an
+ expression; this instance lifetime is limited to the expression evaluation.
+
+ <h2><a name="customization">JEXL Customization</a></h2>
+ If you need to make JEXL treat some objects in a specialized manner or tweak how it
+ reacts to some settings, you can derive most of its inner-workings.
+ <p>
+ {@link org.apache.commons.jexl2.JexlEngine} is meant to be
+ extended and lets you capture your own configuration defaults wrt cache sizes and various flags.
+ Implementing your own cache - instead of the basic LinkedHashMap based one - would be
+ another possible extension.
+ </p>
+ <p>
+ {@link org.apache.commons.jexl2.JexlArithmetic}
+ is the class to derive if you need to change how operators behave. For example, this would
+ be the case if you wanted '+' to operate on arrays; you'd need to derive JexlArithmetic and
+ implement your own version of Add.
+ </p>
+ <p>
+ {@link org.apache.commons.jexl2.Interpreter}
+ is the class to derive if you need to add more features to the evaluation
+ itself; for instance, you want pre- and post- resolvers for variables or nested scopes for
+ for variable contexts or add factory based support to the 'new' operator.
+ </p>
+ <p>
+ {@link org.apache.commons.jexl2.introspection.UberspectImpl}
+ is the class to derive if you need to add introspection or reflection capabilities for some objects.
+ The code already reflects public fields as properties on top of Java-beans conventions.
+ </p>
+ </body>
+</html>
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/parser/JexlNode.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/parser/JexlNode.java
new file mode 100644
index 0000000..06659d6
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/parser/JexlNode.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2.parser;
+
+import org.apache.commons.jexl2.JexlInfo;
+
+/**
+ * Base class for parser nodes - holds an 'image' of the token for later use.
+ *
+ * @since 2.0
+ */
+public abstract class JexlNode extends SimpleNode implements JexlInfo {
+ /** token value. */
+ public String image;
+
+ public JexlNode(int id) {
+ super(id);
+ }
+
+ public JexlNode(Parser p, int id) {
+ super(p, id);
+ }
+
+ public JexlInfo getInfo() {
+ JexlNode node = this;
+ while (node != null) {
+ if (node.value instanceof JexlInfo) {
+ return (JexlInfo) node.value;
+ }
+ node = node.jjtGetParent();
+ }
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ public String debugString() {
+ JexlInfo info = getInfo();
+ return info != null? info.debugString() : "";
+ }
+}
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/parser/Parser.jjt b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/parser/Parser.jjt
new file mode 100644
index 0000000..e0e0626
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/parser/Parser.jjt
@@ -0,0 +1,564 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+/**
+ * Jexl : Java Expression Language
+ *
+ * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a>
+ * @author <a href="mailto:mhw@kremvax.net">Mark H. Wilkinson</a>
+ *
+ * @version $Id$
+ */
+
+options
+{
+ MULTI=true;
+ STATIC=false;
+ VISITOR=true;
+ NODE_SCOPE_HOOK=true;
+ NODE_CLASS="JexlNode";
+ UNICODE_INPUT=true;
+}
+
+PARSER_BEGIN(Parser)
+
+package org.apache.commons.jexl2.parser;
+
+import java.io.Reader;
+import org.apache.commons.jexl2.JexlInfo;
+
+public class Parser extends StringParser
+{
+ public ASTJexlScript parse(Reader reader, JexlInfo info)
+ throws ParseException
+ {
+ ReInit(reader);
+ /*
+ * lets do the 'Unique Init' in here to be
+ * safe - it's a pain to remember
+ */
+
+ ASTJexlScript tree = JexlScript();
+ tree.value = info;
+ return tree;
+ }
+
+ void jjtreeOpenNodeScope(Node n) {}
+ void jjtreeCloseNodeScope(Node n) throws ParseException {
+ if (n instanceof ASTAmbiguous && n.jjtGetNumChildren() > 0) {
+ Token tok = this.getToken(0);
+ StringBuilder strb = new StringBuilder("Ambiguous statement ");
+ if (tok != null) {
+ strb.append("@");
+ strb.append(tok.beginLine);
+ strb.append(":");
+ strb.append(tok.beginColumn);
+ }
+ strb.append(", missing ';' between expressions");
+ throw new ParseException(strb.toString());
+ }
+ }
+}
+
+PARSER_END(Parser)
+
+
+/***************************************
+ * Skip & Number literal tokens
+ ***************************************/
+
+<*> SKIP : /* WHITE SPACE */
+{
+ <"##" (~["\n","\r"])* ("\n" | "\r" | "\r\n")? >
+| <"/*" (~["*"])* "*" ("*" | ~["*","/"] (~["*"])* "*")* "/">
+| <"//" (~["\n","\r"])* ("\n" | "\r" | "\r\n")? >
+| " "
+| "\t"
+| "\n"
+| "\r"
+| "\f"
+}
+
+<*> TOKEN : /* LITERALS */
+{
+ < INTEGER_LITERAL: (<DIGIT>)+ >
+|
+ < FLOAT_LITERAL: (<DIGIT>)+ "."(<DIGIT>)+ >
+}
+
+
+<*> TOKEN : /* KEYWORDS */
+{
+ < IF : "if" >
+ | < ELSE : "else" >
+ | < FOR : "for" >
+ | < FOREACH : "foreach" > : FOR_EACH_IN
+ | < WHILE : "while" >
+ | < NEW : "new" >
+ | < EMPTY : "empty" >
+ | < SIZE : "size" >
+ | < NULL : "null" >
+ | < TRUE : "true" >
+ | < FALSE : "false" >
+}
+
+<*> TOKEN : { /* SEPARATORS */
+ < LPAREN : "(" >
+ | < RPAREN : ")" >
+ | < LCURLY : "{" >
+ | < RCURLY : "}" >
+ | < LBRACKET : "[" >
+ | < RBRACKET : "]" >
+ | < SEMICOL : ";" >
+ | < COLON : ":" >
+ | < COMMA : "," >
+}
+
+<FOR_EACH_IN> TOKEN : /* foreach in */
+{
+ < IN : "in" > : DEFAULT
+}
+
+/***************************************
+ * Statements
+ ***************************************/
+
+ASTJexlScript JexlScript() : {}
+{
+ ( Statement() )* <EOF>
+ { return jjtThis;}
+}
+
+
+void Statement() #void : {}
+{
+ <SEMICOL>
+ | LOOKAHEAD(3) Block()
+ | IfStatement()
+ | ForeachStatement()
+ | WhileStatement()
+ | ExpressionStatement()
+}
+
+void Block() #Block : {}
+{
+ <LCURLY> ( Statement() )* <RCURLY>
+}
+
+
+void ExpressionStatement() #void : {}
+{
+ Expression() (LOOKAHEAD(1) Expression() #Ambiguous())* (LOOKAHEAD(2) <SEMICOL>)?
+}
+
+
+void IfStatement() : {}
+{
+ <IF> <LPAREN> Expression() <RPAREN> Statement() ( LOOKAHEAD(1) <ELSE> Statement() )?
+}
+
+
+void WhileStatement() : {}
+{
+ <WHILE> <LPAREN> Expression() <RPAREN> Statement()
+}
+
+
+void ForeachStatement() : {}
+{
+ <FOR> <LPAREN> Reference() <COLON> Expression() <RPAREN> Statement()
+|
+ <FOREACH> <LPAREN> Reference() <IN> Expression() <RPAREN> Statement()
+}
+
+
+/***************************************
+ * Expression syntax
+ ***************************************/
+
+void Expression() #void : {}
+{
+ LOOKAHEAD( Reference() "=" ) Assignment()
+|
+ ConditionalExpression()
+}
+
+
+void Assignment() #Assignment(2) : {}
+{
+ Reference() "=" Expression()
+}
+
+/***************************************
+ * Conditional & relational
+ ***************************************/
+
+void ConditionalExpression() #void : {}
+{
+ ConditionalOrExpression()
+ (
+ "?" Expression() <COLON> Expression() #TernaryNode(3)
+ |
+ "?:" Expression() #TernaryNode(2)
+ )?
+}
+
+void ConditionalOrExpression() #void :
+{}
+{
+ ConditionalAndExpression()
+ (
+ "||" ConditionalAndExpression() #OrNode(2)
+ |
+ "or" ConditionalAndExpression() #OrNode(2)
+ )*
+}
+
+void ConditionalAndExpression() #void :
+{}
+{
+ InclusiveOrExpression()
+ (
+ "&&" InclusiveOrExpression() #AndNode(2)
+ |
+ "and" InclusiveOrExpression() #AndNode(2)
+ )*
+}
+
+void InclusiveOrExpression() #void :
+{}
+{
+ ExclusiveOrExpression()
+ ( "|" ExclusiveOrExpression() #BitwiseOrNode(2) )*
+}
+
+void ExclusiveOrExpression() #void :
+{}
+{
+ AndExpression()
+ ( "^" AndExpression() #BitwiseXorNode(2) )*
+}
+
+void AndExpression() #void :
+{}
+{
+ EqualityExpression()
+ ( "&" EqualityExpression() #BitwiseAndNode(2) )*
+}
+
+void EqualityExpression() #void :
+{}
+{
+ RelationalExpression()
+ (
+ "==" RelationalExpression() #EQNode(2)
+ |
+ "eq" RelationalExpression() #EQNode(2)
+ |
+ "!=" RelationalExpression() #NENode(2)
+ |
+ "ne" RelationalExpression() #NENode(2)
+ )?
+}
+
+void RelationalExpression() #void :
+{}
+{
+ AdditiveExpression()
+ (
+ "<" AdditiveExpression() #LTNode(2)
+ |
+ "lt" AdditiveExpression() #LTNode(2)
+ |
+ ">" AdditiveExpression() #GTNode(2)
+ |
+ "gt" AdditiveExpression() #GTNode(2)
+ |
+ "<=" AdditiveExpression() #LENode(2)
+ |
+ "le" AdditiveExpression() #LENode(2)
+ |
+ ">=" AdditiveExpression() #GENode(2)
+ |
+ "ge" AdditiveExpression() #GENode(2)
+ |
+ "=~" AdditiveExpression() #ERNode(2) // equals regexp
+ |
+ "!~" AdditiveExpression() #NRNode(2) // not equals regexp
+ )?
+}
+
+/***************************************
+ * Arithmetic
+ ***************************************/
+
+void AdditiveExpression() #AdditiveNode(>1) : {}
+{
+ MultiplicativeExpression() ( LOOKAHEAD(1) AdditiveOperator() MultiplicativeExpression())*
+}
+
+void AdditiveOperator() : {}
+{
+ "+" { jjtThis.image = "+"; }
+|
+ "-" { jjtThis.image = "-"; }
+}
+
+void MultiplicativeExpression() #void : {}
+{
+ UnaryExpression()
+ (
+ "*" UnaryExpression() #MulNode(2)
+ |
+ "/" UnaryExpression() #DivNode(2)
+ |
+ "div" UnaryExpression() #DivNode(2)
+ |
+ "%" UnaryExpression() #ModNode(2)
+ |
+ "mod" UnaryExpression() #ModNode(2)
+ )*
+}
+
+void UnaryExpression() #void : {}
+{
+ "-" UnaryExpression() #UnaryMinusNode(1)
+|
+ "~" UnaryExpression() #BitwiseComplNode(1)
+|
+ "!" UnaryExpression() #NotNode(1)
+|
+ "not" UnaryExpression() #NotNode(1)
+|
+ PrimaryExpression()
+}
+
+
+/***************************************
+ * Identifier & Literals
+ ***************************************/
+
+void Identifier() :
+{
+ Token t;
+}
+{
+ t=<IDENTIFIER>
+ { jjtThis.image = t.image; }
+}
+
+void Literal() #void :
+{
+ Token t;
+}
+{
+ IntegerLiteral()
+|
+ FloatLiteral()
+|
+ BooleanLiteral()
+|
+ StringLiteral()
+|
+ NullLiteral()
+}
+
+void NullLiteral() : {}
+{
+ <NULL>
+}
+
+void BooleanLiteral() #void :
+{}
+{
+ <TRUE> #TrueNode
+|
+ <FALSE> #FalseNode
+}
+
+void IntegerLiteral() :
+{
+ Token t;
+}
+{
+ t=<INTEGER_LITERAL>
+ { jjtThis.image = t.image; }
+}
+
+void FloatLiteral() :
+{
+ Token t;
+}
+{
+ t=<FLOAT_LITERAL>
+ { jjtThis.image = t.image; }
+}
+
+void StringLiteral() :
+{
+ Token t;
+}
+{
+ t=<STRING_LITERAL>
+ { jjtThis.image = Parser.buildString(t.image, true); }
+}
+
+void ArrayLiteral() : {}
+{
+ <LBRACKET> Expression() ( <COMMA> Expression() )* <RBRACKET>
+}
+
+void MapLiteral() : {}
+{
+ <LCURLY> MapEntry() ( <COMMA> MapEntry() )* <RCURLY>
+}
+
+void MapEntry() : {}
+{
+ Expression() <COLON> Expression()
+}
+
+
+/***************************************
+ * Functions & Methods
+ ***************************************/
+
+void EmptyFunction() : {}
+{
+ LOOKAHEAD(3) <EMPTY> <LPAREN> Expression() <RPAREN>
+|
+ <EMPTY> Reference()
+}
+
+void SizeFunction() : {}
+{
+ <SIZE> <LPAREN> Expression() <RPAREN>
+}
+
+void Function() #FunctionNode: {}
+{
+
+ Identifier() <COLON> Identifier() <LPAREN>[ Expression() ( <COMMA> Expression() )* ] <RPAREN>
+}
+
+void Method() #MethodNode: {}
+{
+ Identifier() <LPAREN>[ Expression() ( <COMMA> Expression() )* ] <RPAREN>
+}
+
+void AnyMethod() #void : {}
+{
+ LOOKAHEAD(<SIZE>) SizeMethod()
+ |
+ LOOKAHEAD(Identifier() <LPAREN>) Method()
+}
+
+void SizeMethod() : {}
+{
+ <SIZE> <LPAREN> <RPAREN>
+}
+
+void Constructor() #ConstructorNode() : {}
+{
+ <NEW> <LPAREN>[ Expression() ( <COMMA> Expression() )* ] <RPAREN>
+}
+
+
+/***************************************
+ * References
+ ***************************************/
+
+void PrimaryExpression() #void : {}
+{
+ Literal()
+|
+ LOOKAHEAD(3) Reference()
+|
+ LOOKAHEAD( <LPAREN> ) <LPAREN> Expression() <RPAREN>
+|
+ LOOKAHEAD( <EMPTY> ) EmptyFunction()
+|
+ LOOKAHEAD( <SIZE> ) SizeFunction()
+|
+ LOOKAHEAD( <NEW> <LPAREN> ) Constructor()
+|
+ LOOKAHEAD( <LCURLY> MapEntry() ) MapLiteral()
+|
+ LOOKAHEAD( <LBRACKET> Expression() ) ArrayLiteral()
+}
+
+
+void ArrayAccess() : {}
+{
+ Identifier() (LOOKAHEAD(2) <LBRACKET> Expression() <RBRACKET>)+
+}
+
+void DotReference() #void : {}
+{
+ ("."
+ ( LOOKAHEAD(Identifier() <LBRACKET> )
+ ArrayAccess()
+ |
+ ( LOOKAHEAD(3)
+ AnyMethod()
+ |
+ Identifier()
+ |
+ IntegerLiteral()
+ )
+ )
+ )*
+}
+
+void Reference() : {}
+{
+ ( LOOKAHEAD(<NEW>) Constructor()
+|
+ LOOKAHEAD(Identifier() <LBRACKET> ) ArrayAccess()
+|
+ LOOKAHEAD(Identifier() <COLON> Identifier() <LPAREN>) Function()
+|
+ LOOKAHEAD(Identifier() <LPAREN>) Method()
+|
+ Identifier()
+|
+ LOOKAHEAD(<LCURLY>) MapLiteral()
+|
+ LOOKAHEAD(<LBRACKET>) ArrayLiteral() ) DotReference()
+}
+
+/***************************************
+ * Identifier & String tokens
+ ***************************************/
+
+<*> TOKEN : /* IDENTIFIERS */
+{
+ < IDENTIFIER: <LETTER> (<LETTER>|<DIGIT>)* >
+|
+ < #LETTER: [ "a"-"z", "A"-"Z", "_", "$" ] >
+|
+ < #DIGIT: [ "0"-"9"] >
+}
+
+<*> TOKEN :
+{
+ <STRING_LITERAL :
+ ("\"" ( ~["\"","\n","\r"] | "\\" ["u","n","t","b","r","f","\\","\""] )* "\"" )
+ |
+ ("\'" ( ~["\'","\n","\r"] | "\\" ["u","n","t","b","r","f","\\","\'"])* "\'" )
+ >
+}
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/parser/SimpleNode.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/parser/SimpleNode.java
new file mode 100644
index 0000000..3b3cb4b
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/parser/SimpleNode.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2.parser;
+
+/**
+ * A class originally generated by JJTree:
+ * /* JavaCCOptions:MULTI=true,NODE_USES_PARSER=true,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=,NODE_FACTORY= *\/
+ * Worksaround issue https://javacc.dev.java.net/issues/show_bug.cgi?id=227
+ * As soon as this issue if fixed and the maven plugin uses the correct version of Javacc, this
+ * class can go away.
+ *
+ * The technical goal is to ensure every reference made in the parser was to a JexlNode; unfortunately,
+ * as in javacc 4.1, it still uses a SimpleNode reference in the generated ParserVisitor.
+ * Besides, there is no need to keep the parser around in the node.
+ *
+ * The functional goal is to a allow a <em>volatile</em> value in the node
+ * so it can serve as a last evaluation cache even in multi-threaded executions.
+ */
+public class SimpleNode implements Node {
+ protected JexlNode parent;
+ protected JexlNode[] children;
+ protected int id;
+ /** volatile value so it can be used as a last evaluation cache. */
+ protected volatile Object value;
+
+ public SimpleNode(int i) {
+ id = i;
+ }
+
+ public SimpleNode(Parser p, int i) {
+ this(i);
+ }
+
+ public void jjtOpen() {
+ }
+
+ public void jjtClose() {
+ }
+
+ public void jjtSetParent(Node n) {
+ parent = (JexlNode) n;
+ }
+
+ public JexlNode jjtGetParent() {
+ return parent;
+ }
+
+ public void jjtAddChild(Node n, int i) {
+ if (children == null) {
+ children = new JexlNode[i + 1];
+ } else if (i >= children.length) {
+ JexlNode c[] = new JexlNode[i + 1];
+ System.arraycopy(children, 0, c, 0, children.length);
+ children = c;
+ }
+ children[i] = (JexlNode) n;
+ }
+
+ public JexlNode jjtGetChild(int i) {
+ return children[i];
+ }
+
+ public int jjtGetNumChildren() {
+ return (children == null) ? 0 : children.length;
+ }
+
+ public void jjtSetValue(Object value) {
+ this.value = value;
+ }
+
+ public Object jjtGetValue() {
+ return value;
+ }
+
+ /** Accept the visitor. **/
+ public Object jjtAccept(ParserVisitor visitor, Object data) {
+ return visitor.visit(this, data);
+ }
+
+ /** Accept the visitor. **/
+ public Object childrenAccept(ParserVisitor visitor, Object data) {
+ if (children != null) {
+ for (int i = 0; i < children.length; ++i) {
+ children[i].jjtAccept(visitor, data);
+ }
+ }
+ return data;
+ }
+
+ /* You can override these two methods in subclasses of SimpleNode to
+ customize the way the JexlNode appears when the tree is dumped. If
+ your output uses more than one line you should override
+ toString(String), otherwise overriding toString() is probably all
+ you need to do. */
+
+ @Override
+ public String toString() { return ParserTreeConstants.jjtNodeName[id]; }
+ public String toString(String prefix) { return prefix + toString(); }
+
+ /* Override this method if you want to customize how the JexlNode dumps
+ out its children. */
+
+ public void dump(String prefix) {
+ System.out.println(toString(prefix));
+ if (children != null) {
+ for (int i = 0; i < children.length; ++i) {
+ SimpleNode n = children[i];
+ if (n != null) {
+ n.dump(prefix + " ");
+ }
+ }
+ }
+ }
+}
+
+/* JavaCC - OriginalChecksum=7dff880883d088a37c1e3197e4b455a0 (do not edit this line) */
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/parser/StringParser.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/parser/StringParser.java
new file mode 100644
index 0000000..8ade6fd
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/parser/StringParser.java
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2.parser;
+
+/**
+ * Common constant strings utilities.
+ * <p>
+ * This package methods read JEXL string literals and handle escaping through the
+ * 'backslash' (ie: \) character. Escaping is used to neutralize string delimiters (the single
+ * and double quotes) and read Unicode hexadecimal encoded characters.
+ * </p>
+ * <p>
+ * The only escapable characters are the single and double quotes - ''' and '"' -,
+ * a Unicode sequence starting with 'u' followed by 4 hexadecimals and
+ * the backslash character - '\' - itself.
+ * </p>
+ * <p>
+ * A sequence where '\' occurs before any non-escapable character or sequence has no effect, the
+ * sequence output being the same as the input.
+ * </p>
+ */
+public class StringParser {
+ /** Default constructor. */
+ public StringParser() {}
+
+ /**
+ * Builds a string, handles escaping through '\' syntax.
+ * @param str the string to build from
+ * @param eatsep whether the separator, the first character, should be considered
+ * @return the built string
+ */
+ public static String buildString(CharSequence str, boolean eatsep) {
+ StringBuilder strb = new StringBuilder(str.length());
+ char sep = eatsep ? str.charAt(0) : 0;
+ int end = str.length() - (eatsep ? 1 : 0);
+ int begin = (eatsep ? 1 : 0);
+ read(strb, str, begin, end, sep);
+ return strb.toString();
+ }
+
+ /**
+ * Read the remainder of a string till a given separator,
+ * handles escaping through '\' syntax.
+ * @param strb the destination buffer to copy characters into
+ * @param str the origin
+ * @param index the offset into the origin
+ * @param sep the separator, single or double quote, marking end of string
+ * @return the offset in origin
+ */
+ public static int readString(StringBuilder strb, CharSequence str, int index, char sep) {
+ return read(strb, str, index, str.length(), sep);
+ }
+
+ /**
+ * Read the remainder of a string till a given separator,
+ * handles escaping through '\' syntax.
+ * @param strb the destination buffer to copy characters into
+ * @param str the origin
+ * @param begin the relative offset in str to begin reading
+ * @param end the relative offset in str to end reading
+ * @param sep the separator, single or double quote, marking end of string
+ * @return the last character offset handled in origin
+ */
+ private static int read(StringBuilder strb, CharSequence str, int begin, int end, char sep) {
+ boolean escape = false;
+ int index = begin;
+ for (; index < end; ++index) {
+ char c = str.charAt(index);
+ if (escape) {
+ if (c == 'u' && (index + 4) < end && readUnicodeChar(strb, str, index + 1) > 0) {
+ index += 4;
+ }
+ else {
+ // if c is not an escapable character, re-emmit the backslash before it
+ boolean notSeparator = sep == 0? c != '\'' && c != '"' : c != sep;
+ if (notSeparator && c != '\\' ) {
+ strb.append('\\');
+ }
+ strb.append(c);
+ }
+ escape = false;
+ continue;
+ }
+ if (c == '\\') {
+ escape = true;
+ continue;
+ }
+ strb.append(c);
+ if (c == sep) {
+ break;
+ }
+ }
+ return index;
+ }
+
+ /**
+ * Reads a Unicode escape character.
+ * @param strb the builder to write the character to
+ * @param str the sequence
+ * @param begin the begin offset in sequence (after the '\\u')
+ * @return 0 if char could not be read, 4 otherwise
+ */
+ private static final int readUnicodeChar(StringBuilder strb, CharSequence str, int begin) {
+ char xc = 0;
+ int bits = 12;
+ int value = 0;
+ for(int offset = 0; offset < 4; ++offset) {
+ char c = str.charAt(begin + offset);
+ if (c >= '0' && c <= '9') {
+ value = (c - '0');
+ }
+ else if (c >= 'a' && c <= 'h') {
+ value = (c - 'a' + 10);
+ }
+ else if (c >= 'A' && c <= 'H') {
+ value = (c - 'A' + 10);
+ }
+ else {
+ return 0;
+ }
+ xc |= value << bits;
+ bits -= 4;
+ }
+ strb.append(xc);
+ return 4;
+ }
+
+ /**
+ * Escapes a String representation, expand non-ASCII characters as Unicode escape sequence.
+ * @param str the string to escape
+ * @return the escaped representation
+ */
+ public static String escapeString(String str) {
+ if (str == null) {
+ return null;
+ }
+ final int length = str.length();
+ StringBuilder strb = new StringBuilder(length + 2);
+ strb.append('\'');
+ for (int i = 0; i < length; ++i) {
+ char c = str.charAt(i);
+ if (c < 127) {
+ if (c == '\'') {
+ // escape quote
+ strb.append('\\');
+ strb.append('\'');
+ } else if (c == '\\') {
+ // escape backslash
+ strb.append('\\');
+ strb.append('\\');
+ } else {
+ strb.append(c);
+ }
+ } else {
+ // convert to Unicode escape sequence
+ strb.append('\\');
+ strb.append('u');
+ String hex = Integer.toHexString(c);
+ for (int h = hex.length(); h < 4; ++h) {
+ strb.append('0');
+ }
+ strb.append(hex);
+ }
+ }
+ strb.append('\'');
+ return strb.toString();
+ }
+}
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/parser/package.html b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/parser/package.html
new file mode 100644
index 0000000..b09c836
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/parser/package.html
@@ -0,0 +1,31 @@
+<html>
+ <!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+ -->
+ <head>
+ <title>Package Documentation for org.apache.commons.jexl2.parser Package</title>
+ </head>
+ <body bgcolor="white">
+ <p>
+ Contains the Parser for JEXL script.
+ </p>
+ <p>
+ This internal package is not intended for public usage and there is <b>no</b>
+ guarantee that its public classes or methods will remain as is in subsequent
+ versions.
+ </p>
+ </body>
+</html>
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/scripting/JexlScriptEngine.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/scripting/JexlScriptEngine.java
new file mode 100644
index 0000000..764e9d5
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/scripting/JexlScriptEngine.java
@@ -0,0 +1,376 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2.scripting;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.Writer;
+
+import javax.script.AbstractScriptEngine;
+import javax.script.Bindings;
+import javax.script.Compilable;
+import javax.script.CompiledScript;
+import javax.script.ScriptContext;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineFactory;
+import javax.script.ScriptException;
+import javax.script.SimpleBindings;
+
+import org.apache.commons.jexl2.JexlContext;
+import org.apache.commons.jexl2.JexlEngine;
+import org.apache.commons.jexl2.Script;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Implements the Jexl ScriptEngine for JSF-223.
+ * <p>
+ * This implementation gives access to both ENGINE_SCOPE and GLOBAL_SCOPE bindings.
+ * When a JEXL script accesses a variable for read or write,
+ * this implementation checks first ENGINE and then GLOBAL scope.
+ * The first one found is used.
+ * If no variable is found, and the JEXL script is writing to a variable,
+ * it will be stored in the ENGINE scope.
+ * </p>
+ * <p>
+ * The implementation also creates the "JEXL" script object as an instance of the
+ * class {@link JexlScriptObject} for access to utility methods and variables.
+ * </p>
+ * See
+ * <a href="http://java.sun.com/javase/6/docs/api/javax/script/package-summary.html">Java Scripting API</a>
+ * Javadoc.
+ * @since 2.0
+ */
+public class JexlScriptEngine extends AbstractScriptEngine implements Compilable {
+ /** The logger. */
+ private static final Log LOG = LogFactory.getLog(JexlScriptEngine.class);
+
+ /** The shared expression cache size. */
+ private static final int CACHE_SIZE = 512;
+
+ /** Reserved key for context (mandated by JSR-223). */
+ public static final String CONTEXT_KEY = "context";
+
+ /** Reserved key for JexlScriptObject. */
+ public static final String JEXL_OBJECT_KEY = "JEXL";
+
+ /** The JexlScriptObject instance. */
+ private final JexlScriptObject jexlObject;
+
+ /** The factory which created this instance. */
+ private final ScriptEngineFactory parentFactory;
+
+ /** The JEXL EL engine. */
+ private final JexlEngine jexlEngine;
+
+ /**
+ * Default constructor.
+ * <p>
+ * Only intended for use when not using a factory.
+ * Sets the factory to {@link JexlScriptEngineFactory}.
+ */
+ public JexlScriptEngine() {
+ this(FactorySingletonHolder.DEFAULT_FACTORY);
+ }
+
+ /**
+ * Implements engine and engine context properties for use by JEXL scripts.
+ * Those properties are allways bound to the default engine scope context.
+ * <p>
+ * The following properties are defined:
+ * <ul>
+ * <li>in - refers to the engine scope reader that defaults to reading System.err</li>
+ * <li>out - refers the engine scope writer that defaults to writing in System.out</li>
+ * <li>err - refers to the engine scope writer that defaults to writing in System.err</li>
+ * <li>logger - the JexlScriptEngine logger</li>
+ * <li>System - the System.class</li>
+ * </ul>
+ * </p>
+ * @since 2.0
+ */
+ public class JexlScriptObject {
+ /**
+ * Gives access to the underlying JEXL engine shared between all ScriptEngine instances.
+ * <p>Although this allows to manipulate various engine flags (lenient, debug, cache...)
+ * for <strong>all</strong> JexlScriptEngine instances, you probably should only do so
+ * if you are in strict control and sole user of the Jexl scripting feature.</p>
+ * @return the shared underlying JEXL engine
+ */
+ public JexlEngine getEngine() {
+ return jexlEngine;
+ }
+
+ /**
+ * Gives access to the engine scope output writer (defaults to System.out).
+ * @return the engine output writer
+ */
+ public PrintWriter getOut() {
+ final Writer out = context.getWriter();
+ if (out instanceof PrintWriter) {
+ return (PrintWriter) out;
+ } else if (out != null) {
+ return new PrintWriter(out, true);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Gives access to the engine scope error writer (defaults to System.err).
+ * @return the engine error writer
+ */
+ public PrintWriter getErr() {
+ final Writer error = context.getErrorWriter();
+ if (error instanceof PrintWriter) {
+ return (PrintWriter) error;
+ } else if (error != null) {
+ return new PrintWriter(error, true);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Gives access to the engine scope input reader (defaults to System.in).
+ * @return the engine input reader
+ */
+ public Reader getIn() {
+ return context.getReader();
+ }
+
+ /**
+ * Gives access to System class.
+ * @return System.class
+ */
+ public Class<System> getSystem() {
+ return System.class;
+ }
+
+ /**
+ * Gives access to the engine logger.
+ * @return the JexlScriptEngine logger
+ */
+ public Log getLogger() {
+ return LOG;
+ }
+ }
+
+
+ /**
+ * Create a scripting engine using the supplied factory.
+ *
+ * @param factory the factory which created this instance.
+ * @throws NullPointerException if factory is null
+ */
+ public JexlScriptEngine(final ScriptEngineFactory factory) {
+ if (factory == null) {
+ throw new NullPointerException("ScriptEngineFactory must not be null");
+ }
+ parentFactory = factory;
+ jexlEngine = EngineSingletonHolder.DEFAULT_ENGINE;
+ jexlObject = new JexlScriptObject();
+ }
+
+ /** {@inheritDoc} */
+ public Bindings createBindings() {
+ return new SimpleBindings();
+ }
+
+ /** {@inheritDoc} */
+ public Object eval(final Reader reader, final ScriptContext context) throws ScriptException {
+ // This is mandated by JSR-223 (see SCR.5.5.2 Methods)
+ if (reader == null || context == null) {
+ throw new NullPointerException("script and context must be non-null");
+ }
+ return eval(readerToString(reader), context);
+ }
+
+ /** {@inheritDoc} */
+ public Object eval(final String script, final ScriptContext context) throws ScriptException {
+ // This is mandated by JSR-223 (see SCR.5.5.2 Methods)
+ if (script == null || context == null) {
+ throw new NullPointerException("script and context must be non-null");
+ }
+ // This is mandated by JSR-223 (end of section SCR.4.3.4.1.2 - Script Execution)
+ context.setAttribute(CONTEXT_KEY, context, ScriptContext.ENGINE_SCOPE);
+ try {
+ Script jexlScript = jexlEngine.createScript(script);
+ JexlContext ctxt = new JexlContextWrapper(context);
+ return jexlScript.execute(ctxt);
+ } catch (Exception e) {
+ throw new ScriptException(e.toString());
+ }
+ }
+
+ /** {@inheritDoc} */
+ public ScriptEngineFactory getFactory() {
+ return parentFactory;
+ }
+
+ /** {@inheritDoc} */
+ public CompiledScript compile(final String script) throws ScriptException {
+ // This is mandated by JSR-223
+ if (script == null) {
+ throw new NullPointerException("script must be non-null");
+ }
+ try {
+ Script jexlScript = jexlEngine.createScript(script);
+ return new JexlCompiledScript(jexlScript);
+ } catch (Exception e) {
+ throw new ScriptException(e.toString());
+ }
+ }
+
+ /** {@inheritDoc} */
+ public CompiledScript compile(final Reader script) throws ScriptException {
+ // This is mandated by JSR-223
+ if (script == null) {
+ throw new NullPointerException("script must be non-null");
+ }
+ return compile(readerToString(script));
+ }
+
+ /**
+ * Reads a script.
+ * @param script the script reader
+ * @return the script as a string
+ * @throws ScriptException if an exception occurs during read
+ */
+ private String readerToString(final Reader script) throws ScriptException {
+ try {
+ return JexlEngine.readerToString(script);
+ } catch (IOException e) {
+ throw new ScriptException(e);
+ }
+ }
+
+ /**
+ * Holds singleton JexlScriptEngineFactory (IODH).
+ */
+ private static class FactorySingletonHolder {
+ /** non instantiable. */
+ private FactorySingletonHolder() {}
+ /** The engine factory singleton instance. */
+ private static final JexlScriptEngineFactory DEFAULT_FACTORY = new JexlScriptEngineFactory();
+ }
+
+ /**
+ * Holds singleton JexlScriptEngine (IODH).
+ * <p>A single JEXL engine and Uberspect is shared by all instances of JexlScriptEngine.</p>
+ */
+ private static class EngineSingletonHolder {
+ /** non instantiable. */
+ private EngineSingletonHolder() {}
+ /** The JEXL engine singleton instance. */
+ private static final JexlEngine DEFAULT_ENGINE = new JexlEngine(null, null, null, LOG) {
+ {
+ this.setCache(CACHE_SIZE);
+ }
+ };
+ }
+
+ /**
+ * Wrapper to help convert a JSR-223 ScriptContext into a JexlContext.
+ *
+ * Current implementation only gives access to ENGINE_SCOPE binding.
+ */
+ private final class JexlContextWrapper implements JexlContext {
+ /** The wrapped script context. */
+ private final ScriptContext scriptContext;
+ /**
+ * Creates a context wrapper.
+ * @param theContext the engine context.
+ */
+ private JexlContextWrapper (final ScriptContext theContext){
+ scriptContext = theContext;
+ }
+
+ /** {@inheritDoc} */
+ public Object get(final String name) {
+ final Object o = scriptContext.getAttribute(name);
+ if (JEXL_OBJECT_KEY.equals(name)) {
+ if (o != null) {
+ LOG.warn("JEXL is a reserved variable name, user defined value is ignored");
+ }
+ return jexlObject;
+ }
+ return o;
+ }
+
+ /** {@inheritDoc} */
+ public void set(final String name, final Object value) {
+ int scope = scriptContext.getAttributesScope(name);
+ if (scope == -1) { // not found, default to engine
+ scope = ScriptContext.ENGINE_SCOPE;
+ }
+ scriptContext.getBindings(scope).put(name , value);
+ }
+
+ /** {@inheritDoc} */
+ public boolean has(final String name) {
+ Bindings bnd = scriptContext.getBindings(ScriptContext.ENGINE_SCOPE);
+ return bnd.containsKey(name);
+ }
+
+ }
+
+ /**
+ * Wrapper to help convert a Jexl Script into a JSR-223 CompiledScript.
+ */
+ private final class JexlCompiledScript extends CompiledScript {
+ /** The underlying Jexl expression instance. */
+ private final Script script;
+
+ /**
+ * Creates an instance.
+ * @param theScript to wrap
+ */
+ private JexlCompiledScript(final Script theScript) {
+ script = theScript;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return script.getText();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Object eval(final ScriptContext context) throws ScriptException {
+ // This is mandated by JSR-223 (end of section SCR.4.3.4.1.2 - Script Execution)
+ context.setAttribute(CONTEXT_KEY, context, ScriptContext.ENGINE_SCOPE);
+ try {
+ JexlContext ctxt = new JexlContextWrapper(context);
+ return script.execute(ctxt);
+ } catch (Exception e) {
+ throw new ScriptException(e.toString());
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ScriptEngine getEngine() {
+ return JexlScriptEngine.this;
+ }
+ }
+
+
+}
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/scripting/JexlScriptEngineFactory.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/scripting/JexlScriptEngineFactory.java
new file mode 100644
index 0000000..cdf72de
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/scripting/JexlScriptEngineFactory.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2.scripting;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineFactory;
+import org.apache.commons.jexl2.JexlEngine;
+import org.apache.commons.jexl2.parser.StringParser;
+
+/**
+ * Implements the Jexl ScriptEngineFactory for JSF-223.
+ * <p>
+ * Supports the following:<br.>
+ * Language short names: "JEXL", "Jexl", "jexl" <br/>
+ * Extension: "jexl"
+ * </p>
+ * <p>
+ * See
+ * <a href="http://java.sun.com/javase/6/docs/api/javax/script/package-summary.html">Java Scripting API</a>
+ * Javadoc.
+ * @since 2.0
+ */
+public class JexlScriptEngineFactory implements ScriptEngineFactory {
+
+ /** {@inheritDoc} */
+ public String getEngineName() {
+ return "JEXL Engine";
+ }
+
+ /** {@inheritDoc} */
+ public String getEngineVersion() {
+ return "1.0"; // ensure this is updated if function changes are made to this class
+ }
+
+ /** {@inheritDoc} */
+ public String getLanguageName() {
+ return "JEXL";
+ }
+
+ /** {@inheritDoc} */
+ public String getLanguageVersion() {
+ return "2.0"; // TODO this should be derived from the actual version
+ }
+
+ /** {@inheritDoc} */
+ public String getMethodCallSyntax(String obj, String m, String... args) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(obj);
+ sb.append('.');
+ sb.append(m);
+ sb.append('(');
+ boolean needComma = false;
+ for(String arg : args){
+ if (needComma) {
+ sb.append(',');
+ }
+ sb.append(arg);
+ needComma = true;
+ }
+ sb.append(')');
+ return sb.toString();
+ }
+
+ /** {@inheritDoc} */
+ public List<String> getExtensions() {
+ return Collections.unmodifiableList(Arrays.asList("jexl"));
+ }
+
+ /** {@inheritDoc} */
+ public List<String> getMimeTypes() {
+ return Collections.unmodifiableList(Arrays.asList("application/x-jexl"));
+ }
+
+ /** {@inheritDoc} */
+ public List<String> getNames() {
+ return Collections.unmodifiableList(Arrays.asList("JEXL", "Jexl", "jexl"));
+ }
+
+ /** {@inheritDoc} */
+ public String getOutputStatement(String toDisplay) {
+ if (toDisplay == null) {
+ return "JEXL.out.print(null)";
+ } else {
+ return "JEXL.out.print("+StringParser.escapeString(toDisplay)+")";
+ }
+ }
+
+ /** {@inheritDoc} */
+ public Object getParameter(String key) {
+ if (key.equals(ScriptEngine.ENGINE)) {
+ return getEngineName();
+ } else if (key.equals(ScriptEngine.ENGINE_VERSION)) {
+ return getEngineVersion();
+ } else if (key.equals(ScriptEngine.NAME)) {
+ return getNames();
+ } else if (key.equals(ScriptEngine.LANGUAGE)) {
+ return getLanguageName();
+ } else if(key.equals(ScriptEngine.LANGUAGE_VERSION)) {
+ return getLanguageVersion();
+ } else if (key.equals("THREADING")) {
+ /*
+ * To implement multithreading, the scripting engine context (inherited from AbstractScriptEngine)
+ * would need to be made thread-safe; so would the setContext/getContext methods.
+ * It is easier to share the underlying Uberspect and JEXL engine instance, especially
+ * with an expression cache.
+ */
+ return null;
+ }
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ public String getProgram(String... statements) {
+ StringBuilder sb = new StringBuilder();
+ for(String statement : statements){
+ sb.append(JexlEngine.cleanExpression(statement));
+ if (!statement.endsWith(";")){
+ sb.append(';');
+ }
+ }
+ return sb.toString();
+ }
+
+ /** {@inheritDoc} */
+ public ScriptEngine getScriptEngine() {
+ JexlScriptEngine engine = new JexlScriptEngine(this);
+ return engine;
+ }
+
+}
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/scripting/Main.java b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/scripting/Main.java
new file mode 100644
index 0000000..8ce7f4e
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/scripting/Main.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2.scripting;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.InputStreamReader;
+
+import javax.script.ScriptEngine;
+import javax.script.ScriptException;
+
+/**
+ * Test application for JexlScriptEngine (JSR-223 implementation).
+ * @since 2.0
+ */
+public class Main {
+
+ /**
+ * Test application for JexlScriptEngine (JSR-223 implementation).
+ *
+ * If a single argument is present, it is treated as a filename of a JEXL
+ * script to be evaluated. Any exceptions terminate the application.
+ *
+ * Otherwise, lines are read from standard input and evaluated.
+ * ScriptExceptions are logged, and do not cause the application to exit.
+ * This is done so that interactive testing is easier.
+ *
+ * @param args (optional) filename to evaluate. Stored in the args variable.
+ *
+ * @throws Exception if parsing or IO fail
+ */
+ public static void main(String[] args) throws Exception {
+ JexlScriptEngineFactory fac = new JexlScriptEngineFactory();
+ ScriptEngine engine = fac.getScriptEngine();
+ engine.put("args", args);
+ if (args.length == 1){
+ Object value = engine.eval(new FileReader(args[0]));
+ System.out.println("Return value: "+value);
+ } else {
+ BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
+ String line;
+ System.out.print("> ");
+ while(null != (line=console.readLine())){
+ try {
+ Object value = engine.eval(line);
+ System.out.println("Return value: "+value);
+ } catch (ScriptException e) {
+ System.out.println(e.getLocalizedMessage());
+ }
+ System.out.print("> ");
+ }
+ }
+ }
+}
diff --git a/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/scripting/package.html b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/scripting/package.html
new file mode 100644
index 0000000..65966f8
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/java/org/apache/commons/jexl2/scripting/package.html
@@ -0,0 +1,31 @@
+<html>
+ <!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+ -->
+ <head>
+ <title>Package Documentation for org.apache.commons.jexl2.scripting Package</title>
+ </head>
+ <body bgcolor="white">
+ <p>
+ Contains the JSR-223 Scripting Engine for JEXL script.
+ </p>
+ <p>
+ This internal package is not intended for public usage and there is <b>no</b>
+ guarantee that its public classes or methods will remain as is in subsequent
+ versions.
+ </p>
+ </body>
+</html>
diff --git a/COMMONS_JEXL_2_0/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory b/COMMONS_JEXL_2_0/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory
new file mode 100644
index 0000000..54008cf
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory
@@ -0,0 +1,19 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+#
+
+org.apache.commons.jexl2.scripting.JexlScriptEngineFactory
diff --git a/COMMONS_JEXL_2_0/src/site/resources/images/jexl-logo-white.png b/COMMONS_JEXL_2_0/src/site/resources/images/jexl-logo-white.png
new file mode 100644
index 0000000..3ad2943
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/site/resources/images/jexl-logo-white.png
Binary files differ
diff --git a/COMMONS_JEXL_2_0/src/site/resources/images/jexl-logo-white.xcf b/COMMONS_JEXL_2_0/src/site/resources/images/jexl-logo-white.xcf
new file mode 100644
index 0000000..3edf5c2
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/site/resources/images/jexl-logo-white.xcf
Binary files differ
diff --git a/COMMONS_JEXL_2_0/src/site/site.xml b/COMMONS_JEXL_2_0/src/site/site.xml
new file mode 100644
index 0000000..876baf2
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/site/site.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+-->
+<project name="JEXL">
+ <bannerRight>
+ <name>Commons JEXL</name>
+ <src>images/jexl-logo-white.png</src>
+ <href>index.html</href>
+ </bannerRight>
+
+ <body>
+ <menu name="Commons Jexl">
+ <item name="Overview" href="index.html" />
+ <item name="2.0 Javadoc" href="apidocs/index.html"/>
+ <item name="1.1 Javadoc" href="apidocs-1.1/index.html"/>
+ <item name="Releases and Builds" href="releases.html"/>
+ <item name="Download" href="download_jexl.cgi"/>
+ <item name="Reference" href="reference/index.html"/>
+ <item name="JEXL Wiki" href="http://wiki.apache.org/commons/JEXL"/>
+ </menu>
+
+ <menu name="Development">
+ <item name="Mailing Lists" href="mail-lists.html"/>
+ <item name="Issue Tracking" href="issue-tracking.html"/>
+ <item name="Source Repository" href="source-repository.html"/>
+ <item name="Building" href="building.html"/>
+ <item name="Javadoc (SVN latest)" href="apidocs/index.html"/>
+ </menu>
+
+ </body>
+
+</project>
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/ArithmeticTest.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/ArithmeticTest.java
new file mode 100644
index 0000000..ec40f55
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/ArithmeticTest.java
@@ -0,0 +1,249 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+
+import java.util.Map;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import java.util.HashMap;
+import org.apache.commons.jexl2.junit.Asserter;
+
+
+public class ArithmeticTest extends JexlTestCase {
+ private Asserter asserter;
+
+ @Override
+ public void setUp() {
+ asserter = new Asserter(JEXL);
+ }
+
+ public void testBigDecimal() throws Exception {
+ asserter.setVariable("left", new BigDecimal(2));
+ asserter.setVariable("right", new BigDecimal(6));
+ asserter.assertExpression("left + right", new BigDecimal(8));
+ asserter.assertExpression("right - left", new BigDecimal(4));
+ asserter.assertExpression("right * left", new BigDecimal(12));
+ asserter.assertExpression("right / left", new BigDecimal(3));
+ asserter.assertExpression("right % left", new BigDecimal(0));
+ }
+
+ public void testBigInteger() throws Exception {
+ asserter.setVariable("left", new BigInteger("2"));
+ asserter.setVariable("right", new BigInteger("6"));
+ asserter.assertExpression("left + right", new BigInteger("8"));
+ asserter.assertExpression("right - left", new BigInteger("4"));
+ asserter.assertExpression("right * left", new BigInteger("12"));
+ asserter.assertExpression("right / left", new BigInteger("3"));
+ asserter.assertExpression("right % left", new BigInteger("0"));
+ }
+
+ /**
+ * test some simple mathematical calculations
+ */
+ public void testUnaryMinus() throws Exception {
+ asserter.setVariable("aByte", new Byte((byte) 1));
+ asserter.setVariable("aShort", new Short((short) 2));
+ asserter.setVariable("anInteger", new Integer(3));
+ asserter.setVariable("aLong", new Long(4));
+ asserter.setVariable("aFloat", new Float(5.5));
+ asserter.setVariable("aDouble", new Double(6.6));
+ asserter.setVariable("aBigInteger", new BigInteger("7"));
+ asserter.setVariable("aBigDecimal", new BigDecimal("8.8"));
+
+ asserter.assertExpression("-3", new Integer("-3"));
+ asserter.assertExpression("-3.0", new Float("-3.0"));
+ asserter.assertExpression("-aByte", new Byte((byte) -1));
+ asserter.assertExpression("-aShort", new Short((short) -2));
+ asserter.assertExpression("-anInteger", new Integer(-3));
+ asserter.assertExpression("-aLong", new Long(-4));
+ asserter.assertExpression("-aFloat", new Float(-5.5));
+ asserter.assertExpression("-aDouble", new Double(-6.6));
+ asserter.assertExpression("-aBigInteger", new BigInteger("-7"));
+ asserter.assertExpression("-aBigDecimal", new BigDecimal("-8.8"));
+ }
+
+ /**
+ * test some simple mathematical calculations
+ */
+ public void testCalculations() throws Exception {
+
+ asserter.setVariable("foo", new Integer(2));
+
+ asserter.assertExpression("foo + 2", new Integer(4));
+ asserter.assertExpression("3 + 3", new Integer(6));
+ asserter.assertExpression("3 + 3 + foo", new Integer(8));
+ asserter.assertExpression("3 * 3", new Integer(9));
+ asserter.assertExpression("3 * 3 + foo", new Integer(11));
+ asserter.assertExpression("3 * 3 - foo", new Integer(7));
+
+ /*
+ * test parenthesized exprs
+ */
+ asserter.assertExpression("(4 + 3) * 6", new Integer(42));
+ asserter.assertExpression("(8 - 2) * 7", new Integer(42));
+
+ /*
+ * test some floaty stuff
+ */
+ asserter.assertExpression("3 * \"3.0\"", new Double(9));
+ asserter.assertExpression("3 * 3.0", new Double(9));
+
+ /*
+ * test / and %
+ */
+ asserter.assertExpression("6 / 3", new Integer(6 / 3));
+ asserter.assertExpression("6.4 / 3", new Double(6.4 / 3));
+ asserter.assertExpression("0 / 3", new Integer(0 / 3));
+ asserter.assertExpression("3 / 0", new Double(0));
+ asserter.assertExpression("4 % 3", new Integer(1));
+ asserter.assertExpression("4.8 % 3", new Double(4.8 % 3));
+
+ /*
+ * test new null coersion
+ */
+ asserter.setVariable("imanull", null);
+ asserter.assertExpression("imanull + 2", new Integer(2));
+ asserter.assertExpression("imanull + imanull", new Integer(0));
+ }
+
+ public void testCoercions() throws Exception {
+ asserter.assertExpression("1", new Integer(1)); // numerics default to Integer
+// asserter.assertExpression("5L", new Long(5)); // TODO when implemented
+
+ asserter.setVariable("I2", new Integer(2));
+ asserter.setVariable("L2", new Long(2));
+ asserter.setVariable("L3", new Long(3));
+ asserter.setVariable("B10", BigInteger.TEN);
+
+ // Integer & Integer => Integer
+ asserter.assertExpression("I2 + 2", new Integer(4));
+ asserter.assertExpression("I2 * 2", new Integer(4));
+ asserter.assertExpression("I2 - 2", new Integer(0));
+ asserter.assertExpression("I2 / 2", new Integer(1));
+
+ // Integer & Long => Long
+ asserter.assertExpression("I2 * L2", new Long(4));
+ asserter.assertExpression("I2 / L2", new Long(1));
+
+ // Long & Long => Long
+ asserter.assertExpression("L2 + 3", new Long(5));
+ asserter.assertExpression("L2 + L3", new Long(5));
+ asserter.assertExpression("L2 / L2", new Long(1));
+ asserter.assertExpression("L2 / 2", new Long(1));
+
+ // BigInteger
+ asserter.assertExpression("B10 / 10", BigInteger.ONE);
+ asserter.assertExpression("B10 / I2", new BigInteger("5"));
+ asserter.assertExpression("B10 / L2", new BigInteger("5"));
+ }
+
+
+ public void testRegexp() throws Exception {
+ asserter.setVariable("str", "abc456");
+ asserter.assertExpression("str =~ '.*456'", Boolean.TRUE);
+ asserter.assertExpression("str !~ 'ABC.*'", Boolean.TRUE);
+ asserter.setVariable("match", "abc.*");
+ asserter.setVariable("nomatch", ".*123");
+ asserter.assertExpression("str =~ match", Boolean.TRUE);
+ asserter.assertExpression("str !~ match", Boolean.FALSE);
+ asserter.assertExpression("str !~ nomatch", Boolean.TRUE);
+ asserter.assertExpression("str =~ nomatch", Boolean.FALSE);
+ asserter.setVariable("match", java.util.regex.Pattern.compile("abc.*"));
+ asserter.setVariable("nomatch", java.util.regex.Pattern.compile(".*123"));
+ asserter.assertExpression("str =~ match", Boolean.TRUE);
+ asserter.assertExpression("str !~ match", Boolean.FALSE);
+ asserter.assertExpression("str !~ nomatch", Boolean.TRUE);
+ asserter.assertExpression("str =~ nomatch", Boolean.FALSE);
+ }
+
+ /**
+ *
+ * if silent, all arith exception return 0.0
+ * if not silent, all arith exception throw
+ * @throws Exception
+ */
+ public void testDivideByZero() throws Exception {
+ Map<String,Object> vars = new HashMap<String,Object>();
+ JexlContext context = new MapContext(vars);
+ vars.put("aByte", new Byte((byte) 1));
+ vars.put("aShort", new Short((short) 2));
+ vars.put("aInteger", new Integer(3));
+ vars.put("aLong", new Long(4));
+ vars.put("aFloat", new Float(5.5));
+ vars.put("aDouble", new Double(6.6));
+ vars.put("aBigInteger", new BigInteger("7"));
+ vars.put("aBigDecimal", new BigDecimal("8.8"));
+
+
+ vars.put("zByte", new Byte((byte) 0));
+ vars.put("zShort", new Short((short) 0));
+ vars.put("zInteger", new Integer(0));
+ vars.put("zLong", new Long(0));
+ vars.put("zFloat", new Float(0));
+ vars.put("zDouble", new Double(0));
+ vars.put("zBigInteger", new BigInteger("0"));
+ vars.put("zBigDecimal", new BigDecimal("0"));
+
+ String[] tnames = {
+ "Byte", "Short", "Integer", "Long",
+ "Float", "Double",
+ "BigInteger", "BigDecimal"
+ };
+ // number of permutations this will generate
+ final int PERMS = tnames.length * tnames.length;
+
+ JexlEngine jexl = new JexlEngine();
+ jexl.setCache(128);
+ jexl.setSilent(false);
+ // for non-silent, silent...
+ for (int s = 0; s < 2; ++s) {
+ jexl.setLenient(s == 0);
+ int zthrow = 0;
+ int zeval = 0;
+ // for vars of all types...
+ for (String vname : tnames) {
+ // for zeros of all types...
+ for (String zname : tnames) {
+ // divide var by zero
+ String expr = "a" + vname + " / " + "z" + zname;
+ try {
+ Expression zexpr = jexl.createExpression(expr);
+ Object nan = zexpr.evaluate(context);
+ // check we have a zero & incremement zero count
+ if (nan instanceof Number) {
+ double zero = ((Number) nan).doubleValue();
+ if (zero == 0.0)
+ zeval += 1;
+ }
+ }
+ catch (Exception any) {
+ // increment the exception count
+ zthrow += 1;
+ }
+ }
+ }
+ if (!jexl.isLenient())
+ assertTrue("All expressions should have thrown " + zthrow + "/" + PERMS,
+ zthrow == PERMS);
+ else
+ assertTrue("All expressions should have zeroed " + zeval + "/" + PERMS,
+ zeval == PERMS);
+ }
+ debuggerCheck(jexl);
+ }
+}
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/ArrayAccessTest.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/ArrayAccessTest.java
new file mode 100644
index 0000000..68f3a74
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/ArrayAccessTest.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.jexl2.junit.Asserter;
+
+
+/**
+ * Tests for array access operator []
+ *
+ * @since 2.0
+ */
+public class ArrayAccessTest extends JexlTestCase {
+
+ private Asserter asserter;
+
+ private static final String GET_METHOD_STRING = "GetMethod string";
+
+ // Needs to be accessible by Foo.class
+ static final String[] GET_METHOD_ARRAY =
+ new String[] { "One", "Two", "Three" };
+
+ // Needs to be accessible by Foo.class
+ static final String[][] GET_METHOD_ARRAY2 =
+ new String[][] { {"One", "Two", "Three"},{"Four", "Five", "Six"} };
+
+ @Override
+ public void setUp() {
+ asserter = new Asserter(JEXL);
+ }
+
+ /**
+ * test simple array access
+ */
+ public void testArrayAccess() throws Exception {
+
+ /*
+ * test List access
+ */
+
+ List<Integer> l = new ArrayList<Integer>();
+ l.add(new Integer(1));
+ l.add(new Integer(2));
+ l.add(new Integer(3));
+
+ asserter.setVariable("list", l);
+
+ asserter.assertExpression("list[1]", new Integer(2));
+ asserter.assertExpression("list[1+1]", new Integer(3));
+ asserter.setVariable("loc", new Integer(1));
+ asserter.assertExpression("list[loc+1]", new Integer(3));
+
+ /*
+ * test array access
+ */
+
+ String[] args = { "hello", "there" };
+ asserter.setVariable("array", args);
+ asserter.assertExpression("array[0]", "hello");
+
+ /*
+ * to think that this was an intentional syntax...
+ */
+ asserter.assertExpression("array.0", "hello");
+
+ /*
+ * test map access
+ */
+ Map<String, String> m = new HashMap<String, String>();
+ m.put("foo", "bar");
+
+ asserter.setVariable("map", m);
+ asserter.setVariable("key", "foo");
+
+ asserter.assertExpression("map[\"foo\"]", "bar");
+ asserter.assertExpression("map[key]", "bar");
+
+ /*
+ * test bean access
+ */
+ asserter.setVariable("foo", new Foo());
+ asserter.assertExpression("foo[\"bar\"]", GET_METHOD_STRING);
+ asserter.assertExpression("foo[\"bar\"] == foo.bar", Boolean.TRUE);
+ }
+
+ /**
+ * test some simple double array lookups
+ */
+ public void testDoubleArrays() throws Exception {
+ Object[][] foo = new Object[2][2];
+ foo[0][0] = "one";
+ foo[0][1] = "two";
+
+ asserter.setVariable("foo", foo);
+
+ asserter.assertExpression("foo[0][1]", "two");
+ }
+
+ public void testArrayProperty() throws Exception {
+ Foo foo = new Foo();
+
+ asserter.setVariable("foo", foo);
+
+ asserter.assertExpression("foo.array[1]", GET_METHOD_ARRAY[1]);
+ asserter.assertExpression("foo.array.1", GET_METHOD_ARRAY[1]);
+ asserter.assertExpression("foo.array2[1][1]", GET_METHOD_ARRAY2[1][1]);
+ // asserter.assertExpression("foo.array2.1.1", GET_METHOD_ARRAY2[1][1]);
+ }
+
+ // This is JEXL-26
+ public void testArrayAndDottedConflict() throws Exception {
+ Object[] objects = new Object[] {"an", "array", new Long(0)};
+
+ asserter.setVariable("objects", objects);
+ asserter.setVariable("status", "Enabled");
+ asserter.assertExpression("objects[1].status", null);
+
+ asserter.setVariable("base.status", "Ok");
+ asserter.assertExpression("base.objects[1].status", null);
+ }
+
+ public void testArrayMethods() throws Exception {
+ Object[] objects = new Object[] {"an", "array", new Long(0)};
+
+ asserter.setVariable("objects", objects);
+ asserter.assertExpression("objects.get(1)", "array");
+ asserter.assertExpression("objects.size()", new Integer(3));
+ // setting an index returns the old value
+ asserter.assertExpression("objects.set(1, 'dion')", "array");
+ asserter.assertExpression("objects[1]", "dion");
+ }
+}
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/ArrayLiteralTest.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/ArrayLiteralTest.java
new file mode 100644
index 0000000..3969c29
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/ArrayLiteralTest.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+
+import java.util.Arrays;
+
+/**
+ * Tests for array literals
+ * @since 2.0
+ */
+public class ArrayLiteralTest extends JexlTestCase {
+
+ public void testLiteralWithStrings() throws Exception {
+ Expression e = JEXL.createExpression( "[ 'foo' , 'bar' ]" );
+ JexlContext jc = new MapContext();
+
+ Object o = e.evaluate( jc );
+ Object[] check = { "foo", "bar" };
+ assertTrue( Arrays.equals(check, (Object[])o) );
+ }
+
+ public void testLiteralWithOneEntry() throws Exception {
+ Expression e = JEXL.createExpression( "[ 'foo' ]" );
+ JexlContext jc = new MapContext();
+
+ Object o = e.evaluate( jc );
+ Object[] check = { "foo" };
+ assertTrue( Arrays.equals(check, (Object[])o) );
+ }
+
+ public void testLiteralWithNumbers() throws Exception {
+ Expression e = JEXL.createExpression( "[ 5.0 , 10 ]" );
+ JexlContext jc = new MapContext();
+
+ Object o = e.evaluate( jc );
+ Object[] check = { new Float(5), new Integer(10) };
+ assertTrue( Arrays.equals(check, (Object[])o) );
+ }
+
+ public void testLiteralWithIntegers() throws Exception {
+ Expression e = JEXL.createExpression( "[ 5 , 10 ]" );
+ JexlContext jc = new MapContext();
+
+ Object o = e.evaluate( jc );
+ int[] check = { 5, 10 };
+ assertTrue( Arrays.equals(check, (int[])o) );
+ }
+
+ public void testSizeOfSimpleArrayLiteral() throws Exception {
+ Expression e = JEXL.createExpression( "size([ 'foo' , 'bar' ])" );
+ JexlContext jc = new MapContext();
+
+ Object o = e.evaluate( jc );
+ assertEquals( new Integer( 2 ), o );
+ }
+
+ public void notestCallingMethodsOnNewMapLiteral() throws Exception {
+ Expression e = JEXL.createExpression( "size({ 'foo' : 'bar' }.values())" );
+ JexlContext jc = new MapContext();
+
+ Object o = e.evaluate( jc );
+ assertEquals( new Integer( 1 ), o );
+ }
+
+ public void testNotEmptySimpleArrayLiteral() throws Exception {
+ Expression e = JEXL.createExpression( "empty([ 'foo' , 'bar' ])" );
+ JexlContext jc = new MapContext();
+
+ Object o = e.evaluate( jc );
+ assertFalse( ( (Boolean) o ).booleanValue() );
+ }
+
+}
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/AssignTest.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/AssignTest.java
new file mode 100644
index 0000000..c4ee2a3
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/AssignTest.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+
+/**
+ * Test cases for assignment.
+ *
+ * @author Dion Gillard
+ * @since 1.1
+ */
+public class AssignTest extends JexlTestCase {
+ private static final JexlEngine ENGINE = new JexlEngine();
+ static {
+ ENGINE.setSilent(false);
+ ENGINE.setLenient(false);
+ }
+
+ public static class Froboz {
+ int value;
+ public Froboz(int v) {
+ value = v;
+ }
+ public void setValue(int v) {
+ value = v;
+ }
+ public int getValue() {
+ return value;
+ }
+ }
+
+ public static class Quux {
+ String str;
+ Froboz froboz;
+ public Quux(String str, int fro) {
+ this.str = str;
+ froboz = new Froboz(fro);
+ }
+
+ public Froboz getFroboz() {
+ return froboz;
+ }
+
+ public void setFroboz(Froboz froboz) {
+ this.froboz = froboz;
+ }
+
+ public String getStr() {
+ return str;
+ }
+
+ public void setStr(String str) {
+ this.str = str;
+ }
+ }
+
+ public AssignTest(String testName) {
+ super(testName);
+ }
+
+ /**
+ * Make sure bean assignment works
+ *
+ * @throws Exception on any error
+ */
+ public void testAntish() throws Exception {
+ Expression assign = ENGINE.createExpression("froboz.value = 10");
+ Expression check = ENGINE.createExpression("froboz.value");
+ JexlContext jc = new MapContext();
+ Object o = assign.evaluate(jc);
+ assertEquals("Result is not 10", new Integer(10), o);
+ o = check.evaluate(jc);
+ assertEquals("Result is not 10", new Integer(10), o);
+ }
+
+ public void testBeanish() throws Exception {
+ Expression assign = ENGINE.createExpression("froboz.value = 10");
+ Expression check = ENGINE.createExpression("froboz.value");
+ JexlContext jc = new MapContext();
+ Froboz froboz = new Froboz(-169);
+ jc.set("froboz", froboz);
+ Object o = assign.evaluate(jc);
+ assertEquals("Result is not 10", new Integer(10), o);
+ o = check.evaluate(jc);
+ assertEquals("Result is not 10", new Integer(10), o);
+ }
+
+ public void testAmbiguous() throws Exception {
+ Expression assign = ENGINE.createExpression("froboz.nosuchbean = 10");
+ JexlContext jc = new MapContext();
+ Froboz froboz = new Froboz(-169);
+ jc.set("froboz", froboz);
+ Object o = null;
+ try {
+ o = assign.evaluate(jc);
+ }
+ catch(RuntimeException xrt) {
+ String str = xrt.toString();
+ assertTrue(str.contains("nosuchbean"));
+ return;
+ }
+ finally {
+ assertEquals("Should have failed", null, o);
+ }
+ }
+
+ public void testArray() throws Exception {
+ Expression assign = ENGINE.createExpression("froboz[\"value\"] = 10");
+ Expression check = ENGINE.createExpression("froboz[\"value\"]");
+ JexlContext jc = new MapContext();
+ Froboz froboz = new Froboz(0);
+ jc.set("froboz", froboz);
+ Object o = assign.evaluate(jc);
+ assertEquals("Result is not 10", new Integer(10), o);
+ o = check.evaluate(jc);
+ assertEquals("Result is not 10", new Integer(10), o);
+ }
+
+ public void testMore() throws Exception {
+ JexlContext jc = new MapContext();
+ jc.set("quuxClass", Quux.class);
+ Expression create = ENGINE.createExpression("quux = new(quuxClass, 'xuuq', 100)");
+ Expression assign = ENGINE.createExpression("quux.froboz.value = 10");
+ Expression check = ENGINE.createExpression("quux[\"froboz\"].value");
+
+ Quux quux = (Quux) create.evaluate(jc);
+ assertNotNull("quux is null", quux);
+ Object o = assign.evaluate(jc);
+ assertEquals("Result is not 10", new Integer(10), o);
+ o = check.evaluate(jc);
+ assertEquals("Result is not 10", new Integer(10), o);
+ }
+
+ public void testUtil() throws Exception {
+ Quux quux = ENGINE.newInstance(Quux.class, "xuuq", Integer.valueOf(100));
+ ENGINE.setProperty(quux, "froboz.value", Integer.valueOf(100));
+ Object o = ENGINE.getProperty(quux, "froboz.value");
+ assertEquals("Result is not 100", new Integer(100), o);
+ ENGINE.setProperty(quux, "['froboz'].value", Integer.valueOf(1000));
+ o = ENGINE.getProperty(quux, "['froboz']['value']");
+ assertEquals("Result is not 1000", new Integer(1000), o);
+ }
+
+
+}
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/BitwiseOperatorTest.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/BitwiseOperatorTest.java
new file mode 100644
index 0000000..7248d6e
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/BitwiseOperatorTest.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2;
+import org.apache.commons.jexl2.junit.Asserter;
+
+/**
+ * Tests for the bitwise operators.
+ * @author Dion Gillard
+ * @since 1.1
+ */
+public class BitwiseOperatorTest extends JexlTestCase {
+ private Asserter asserter;
+
+ @Override
+ public void setUp() {
+ asserter = new Asserter(JEXL);
+ }
+
+ /**
+ * Create the named test.
+ * @param name test name
+ */
+ public BitwiseOperatorTest(String name) {
+ super(name);
+ }
+
+ public void testAndWithTwoNulls() throws Exception {
+ asserter.assertExpression("null & null", new Long(0));
+ }
+
+ public void testAndWithLeftNull() throws Exception {
+ asserter.assertExpression("null & 1", new Long(0));
+ }
+
+ public void testAndWithRightNull() throws Exception {
+ asserter.assertExpression("1 & null", new Long(0));
+ }
+
+ public void testAndSimple() throws Exception {
+ asserter.assertExpression("15 & 3", new Long(15 & 3));
+ }
+
+ public void testAndVariableNumberCoercion() throws Exception {
+ asserter.setVariable("x", new Integer(15));
+ asserter.setVariable("y", new Short((short)7));
+ asserter.assertExpression("x & y", new Long(15 & 7));
+ }
+
+ public void testAndVariableStringCoercion() throws Exception {
+ asserter.setVariable("x", new Integer(15));
+ asserter.setVariable("y", "7");
+ asserter.assertExpression("x & y", new Long(15 & 7));
+ }
+
+ public void testComplementWithNull() throws Exception {
+ asserter.assertExpression("~null", new Long(-1));
+ }
+
+ public void testComplementSimple() throws Exception {
+ asserter.assertExpression("~128", new Long(-129));
+ }
+
+ public void testComplementVariableNumberCoercion() throws Exception {
+ asserter.setVariable("x", new Integer(15));
+ asserter.assertExpression("~x", new Long(~15));
+ }
+
+ public void testComplementVariableStringCoercion() throws Exception {
+ asserter.setVariable("x", "15");
+ asserter.assertExpression("~x", new Long(~15));
+ }
+
+ public void testOrWithTwoNulls() throws Exception {
+ asserter.assertExpression("null | null", new Long(0));
+ }
+
+ public void testOrWithLeftNull() throws Exception {
+ asserter.assertExpression("null | 1", new Long(1));
+ }
+
+ public void testOrWithRightNull() throws Exception {
+ asserter.assertExpression("1 | null", new Long(1));
+ }
+
+ public void testOrSimple() throws Exception {
+ asserter.assertExpression("12 | 3", new Long(15));
+ }
+
+ public void testOrVariableNumberCoercion() throws Exception {
+ asserter.setVariable("x", new Integer(12));
+ asserter.setVariable("y", new Short((short) 3));
+ asserter.assertExpression("x | y", new Long(15));
+ }
+
+ public void testOrVariableStringCoercion() throws Exception {
+ asserter.setVariable("x", new Integer(12));
+ asserter.setVariable("y", "3");
+ asserter.assertExpression("x | y", new Long(15));
+ }
+
+ public void testXorWithTwoNulls() throws Exception {
+ asserter.assertExpression("null ^ null", new Long(0));
+ }
+
+ public void testXorWithLeftNull() throws Exception {
+ asserter.assertExpression("null ^ 1", new Long(1));
+ }
+
+ public void testXorWithRightNull() throws Exception {
+ asserter.assertExpression("1 ^ null", new Long(1));
+ }
+
+ public void testXorSimple() throws Exception {
+ asserter.assertExpression("1 ^ 3", new Long(1 ^ 3));
+ }
+
+ public void testXorVariableNumberCoercion() throws Exception {
+ asserter.setVariable("x", new Integer(1));
+ asserter.setVariable("y", new Short((short) 3));
+ asserter.assertExpression("x ^ y", new Long(1 ^ 3));
+ }
+
+ public void testXorVariableStringCoercion() throws Exception {
+ asserter.setVariable("x", new Integer(1));
+ asserter.setVariable("y", "3");
+ asserter.assertExpression("x ^ y", new Long(1 ^ 3));
+ }
+
+ public void testParenthesized() throws Exception {
+ asserter.assertExpression("(2 | 1) & 3", Long.valueOf(3L));
+ asserter.assertExpression("(2 & 1) | 3", Long.valueOf(3L));
+ asserter.assertExpression("~(120 | 42)", new Long( ~(120 | 42) ));
+ }
+
+}
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/BlockTest.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/BlockTest.java
new file mode 100644
index 0000000..c38d21f
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/BlockTest.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+
+/**
+ * Tests for blocks
+ * @since 1.1
+ */
+public class BlockTest extends JexlTestCase {
+
+ /**
+ * Create the test
+ *
+ * @param testName name of the test
+ */
+ public BlockTest(String testName) {
+ super(testName);
+ }
+
+ public void testBlockSimple() throws Exception {
+ Expression e = JEXL.createExpression("if (true) { 'hello'; }");
+ JexlContext jc = new MapContext();
+ Object o = e.evaluate(jc);
+ assertEquals("Result is wrong", "hello", o);
+ }
+
+ public void testBlockExecutesAll() throws Exception {
+ Expression e = JEXL.createExpression("if (true) { x = 'Hello'; y = 'World';}");
+ JexlContext jc = new MapContext();
+ Object o = e.evaluate(jc);
+ assertEquals("First result is wrong", "Hello", jc.get("x"));
+ assertEquals("Second result is wrong", "World", jc.get("y"));
+ assertEquals("Block result is wrong", "World", o);
+ }
+
+ public void testEmptyBlock() throws Exception {
+ Expression e = JEXL.createExpression("if (true) { }");
+ JexlContext jc = new MapContext();
+ Object o = e.evaluate(jc);
+ assertNull("Result is wrong", o);
+ }
+
+ public void testBlockLastExecuted01() throws Exception {
+ Expression e = JEXL.createExpression("if (true) { x = 1; } else { x = 2; }");
+ JexlContext jc = new MapContext();
+ Object o = e.evaluate(jc);
+ assertEquals("Block result is wrong", new Integer(1), o);
+ }
+
+ public void testBlockLastExecuted02() throws Exception {
+ Expression e = JEXL.createExpression("if (false) { x = 1; } else { x = 2; }");
+ JexlContext jc = new MapContext();
+ Object o = e.evaluate(jc);
+ assertEquals("Block result is wrong", new Integer(2), o);
+ }
+
+ public void testNestedBlock() throws Exception {
+ Expression e = JEXL.createExpression("if (true) { x = 'hello'; y = 'world';" + " if (true) { x; } y; }");
+ JexlContext jc = new MapContext();
+ Object o = e.evaluate(jc);
+ assertEquals("Block result is wrong", "world", o);
+ }
+}
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/CacheTest.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/CacheTest.java
new file mode 100644
index 0000000..2c7c7bb
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/CacheTest.java
@@ -0,0 +1,667 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+
+import java.util.List;
+import java.util.Map;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Verifies cache & tryExecute
+ */
+public class CacheTest extends JexlTestCase {
+ public CacheTest(String testName) {
+ super(testName);
+ }
+ private static final JexlEngine jexl = new JexlEngine();
+
+ static {
+ jexl.setCache(512);
+ jexl.setLenient(false);
+ jexl.setSilent(false);
+ }
+
+ // LOOPS & THREADS
+ private static final int LOOPS = 4096;
+ private static final int NTHREADS = 4;
+ // A pseudo random mix of accessors
+ private static final int[] MIX = {
+ 0, 0, 3, 3, 4, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 1, 1, 1, 2, 2, 2,
+ 3, 3, 3, 4, 4, 4, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 2, 2, 3, 3, 0
+ };
+
+ @Override
+ protected void tearDown() throws Exception {
+ debuggerCheck(jexl);
+ }
+
+ /**
+ * A set of classes that define different getter/setter methods for the same properties.
+ * The goal is to verify that the cached JexlPropertyGet / JexlPropertySet in the AST Nodes are indeed
+ * volatile and do not generate errors even when multiple threads concurently hammer them.
+ */
+ public static class Cached {
+ public String compute(String arg) {
+ if (arg == null) {
+ arg = "na";
+ }
+ return getClass().getSimpleName() + "@s#" + arg;
+ }
+
+ public String compute(String arg0, String arg1) {
+ if (arg0 == null) {
+ arg0 = "na";
+ }
+ if (arg1 == null) {
+ arg1 = "na";
+ }
+ return getClass().getSimpleName() + "@s#" + arg0 + ",s#" + arg1;
+ }
+
+ public String compute(Integer arg) {
+ return getClass().getSimpleName() + "@i#" + arg;
+ }
+
+ public String compute(float arg) {
+ return getClass().getSimpleName() + "@f#" + arg;
+ }
+
+ public String compute(int arg0, int arg1) {
+ return getClass().getSimpleName() + "@i#" + arg0 + ",i#" + arg1;
+ }
+
+ public String ambiguous(Integer arg0, int arg1) {
+ return getClass().getSimpleName() + "!i#" + arg0 + ",i#" + arg1;
+ }
+
+ public String ambiguous(int arg0, Integer arg1) {
+ return getClass().getSimpleName() + "!i#" + arg0 + ",i#" + arg1;
+ }
+
+ public static String COMPUTE(String arg) {
+ if (arg == null) {
+ arg = "na";
+ }
+ return "CACHED@s#" + arg;
+ }
+
+ public static String COMPUTE(String arg0, String arg1) {
+ if (arg0 == null) {
+ arg0 = "na";
+ }
+ if (arg1 == null) {
+ arg1 = "na";
+ }
+ return "CACHED@s#" + arg0 + ",s#" + arg1;
+ }
+
+ public static String COMPUTE(int arg) {
+ return "CACHED@i#" + arg;
+ }
+
+ public static String COMPUTE(int arg0, int arg1) {
+ return "CACHED@i#" + arg0 + ",i#" + arg1;
+ }
+ }
+
+ public static class Cached0 extends Cached {
+ protected String value = "Cached0:new";
+ protected Boolean flag = Boolean.FALSE;
+
+ public Cached0() {
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String arg) {
+ if (arg == null) {
+ arg = "na";
+ }
+ value = "Cached0:" + arg;
+ }
+
+ public void setFlag(boolean b) {
+ flag = Boolean.valueOf(b);
+ }
+
+ public boolean isFlag() {
+ return flag.booleanValue();
+ }
+ }
+
+ public static class Cached1 extends Cached0 {
+ @Override
+ public void setValue(String arg) {
+ if (arg == null) {
+ arg = "na";
+ }
+ value = "Cached1:" + arg;
+ }
+ }
+
+ public static class Cached2 extends Cached {
+ boolean flag = false;
+ protected String value;
+
+ public Cached2() {
+ value = "Cached2:new";
+ }
+
+ public Object get(String prop) {
+ if ("value".equals(prop)) {
+ return value;
+ } else if ("flag".equals(prop)) {
+ return Boolean.valueOf(flag);
+ }
+ throw new RuntimeException("no such property");
+ }
+
+ public void set(String p, Object v) {
+ if (v == null) {
+ v = "na";
+ }
+ if ("value".equals(p)) {
+ value = getClass().getSimpleName() + ":" + v;
+ } else if ("flag".equals(p)) {
+ flag = Boolean.parseBoolean(v.toString());
+ } else {
+ throw new RuntimeException("no such property");
+ }
+ }
+ }
+
+ public static class Cached3 extends java.util.TreeMap<String, Object> {
+ private static final long serialVersionUID = 1L;
+ boolean flag = false;
+
+ public Cached3() {
+ put("value", "Cached3:new");
+ put("flag", "false");
+ }
+
+ @Override
+ public Object get(Object key) {
+ return super.get(key.toString());
+ }
+
+ @Override
+ public Object put(String key, Object arg) {
+ if (arg == null) {
+ arg = "na";
+ }
+ arg = "Cached3:" + arg;
+ return super.put(key, arg);
+ }
+
+ public void setflag(boolean b) {
+ flag = b;
+ }
+
+ public boolean isflag() {
+ return flag;
+ }
+ }
+
+ public static class Cached4 extends java.util.ArrayList<String> {
+ private static final long serialVersionUID = 1L;
+
+ public Cached4() {
+ super.add("Cached4:new");
+ super.add("false");
+ }
+
+ public String getValue() {
+ return super.get(0);
+ }
+
+ public void setValue(String arg) {
+ if (arg == null) {
+ arg = "na";
+ }
+ super.set(0, "Cached4:" + arg);
+ }
+
+ public void setflag(Boolean b) {
+ super.set(1, b.toString());
+ }
+
+ public boolean isflag() {
+ return Boolean.parseBoolean(super.get(1));
+ }
+ }
+
+ /**
+ * A helper class to pass arguments in tests (instances of getter/setter exercising classes).
+ */
+ static class TestCacheArguments {
+ Cached0 c0 = new Cached0();
+ Cached1 c1 = new Cached1();
+ Cached2 c2 = new Cached2();
+ Cached3 c3 = new Cached3();
+ Cached4 c4 = new Cached4();
+ Object[] ca = {
+ c0, c1, c2, c3, c4
+ };
+ Object[] value = null;
+ }
+
+ /**
+ * Run same test function in NTHREADS in parallel.
+ * @param ctask the task / test
+ * @param loops number of loops to perform
+ * @param cache whether jexl cache is used or not
+ * @throws Exception if anything goes wrong
+ */
+ void runThreaded(Class<? extends Task> ctask, int loops, boolean cache) throws Exception {
+ if (loops == 0) {
+ loops = MIX.length;
+ }
+ if (cache) {
+ jexl.setCache(32);
+ } else {
+ jexl.setCache(0);
+ }
+ java.util.concurrent.ExecutorService execs = java.util.concurrent.Executors.newFixedThreadPool(NTHREADS);
+ List<Callable<Integer>> tasks = new ArrayList<Callable<Integer>>(NTHREADS);
+ for(int t = 0; t < NTHREADS; ++t) {
+ tasks.add(jexl.newInstance(ctask, loops));
+ }
+ // let's not wait for more than a minute
+ List<Future<Integer>> futures = execs.invokeAll(tasks, 60, TimeUnit.SECONDS);
+ // check that all returned loops
+ for(Future<Integer> future : futures) {
+ assertEquals(Integer.valueOf(loops), future.get());
+ }
+ }
+
+ /**
+ * The base class for MT tests.
+ */
+ public abstract static class Task implements Callable<Integer> {
+ final TestCacheArguments args = new TestCacheArguments();
+ final int loops;
+ final Map<String, Object> vars = new HashMap<String, Object>();
+ final JexlContext jc = new MapContext(vars);
+
+ Task(int loops) {
+ this.loops = loops;
+ }
+
+ public abstract Integer call() throws Exception;
+
+ /**
+ * The actual test function; assigns and checks.
+ * <p>The expression will be evaluated against different classes in parallel.
+ * This verifies that neither the volatile cache in the AST nor the expression cache in the JEXL engine
+ * induce errors.</p>
+ * <p>
+ * Using it as a micro benchmark, it shows creating expression as the dominating cost; the expression
+ * cache takes care of this.
+ * By moving the expression creations out of the main loop, it also shows that the volatile cache speeds
+ * things up around 2x.
+ * </p>
+ * @param value the argument value to control
+ * @return the number of loops performed
+ */
+ public Integer runAssign(Object value) {
+ args.value = new Object[]{value};
+ Object result;
+
+ Expression cacheGetValue = jexl.createExpression("cache.value");
+ Expression cacheSetValue = jexl.createExpression("cache.value = value");
+ for (int l = 0; l < loops; ++l) {
+ int px = (int) Thread.currentThread().getId();
+ int mix = MIX[(l + px) % MIX.length];
+
+ vars.put("cache", args.ca[mix]);
+ vars.put("value", args.value[0]);
+ result = cacheSetValue.evaluate(jc);
+ if (args.value[0] == null) {
+ assertNull(cacheSetValue.toString(), result);
+ } else {
+ assertEquals(cacheSetValue.toString(), args.value[0], result);
+ }
+
+ result = cacheGetValue.evaluate(jc);
+ if (args.value[0] == null) {
+ assertEquals(cacheGetValue.toString(), "Cached" + mix + ":na", result);
+ } else {
+ assertEquals(cacheGetValue.toString(), "Cached" + mix + ":" + args.value[0], result);
+ }
+
+ }
+
+ return Integer.valueOf(loops);
+ }
+ }
+
+ /**
+ * A task to check assignment.
+ */
+ public static class AssignTask extends Task {
+ public AssignTask(int loops) {
+ super(loops);
+ }
+ public Integer call() throws Exception {
+ return runAssign("foo");
+ }
+ }
+
+ /**
+ * A task to check null assignment.
+ */
+ public static class AssignNullTask extends Task {
+ public AssignNullTask(int loops) {
+ super(loops);
+ }
+ public Integer call() throws Exception {
+ return runAssign(null);
+ }
+ }
+
+ /**
+ * A task to check boolean assignment.
+ */
+ public static class AssignBooleanTask extends Task {
+ public AssignBooleanTask(int loops) {
+ super(loops);
+ }
+ public Integer call() throws Exception {
+ return runAssignBoolean(Boolean.TRUE);
+ }
+
+ /** The actual test function. */
+ private Integer runAssignBoolean(Boolean value) {
+ args.value = new Object[]{value};
+ Expression cacheGetValue = jexl.createExpression("cache.flag");
+ Expression cacheSetValue = jexl.createExpression("cache.flag = value");
+ Object result;
+
+ for (int l = 0; l < loops; ++l) {
+ int px = (int) Thread.currentThread().getId();
+ int mix = MIX[(l + px) % MIX.length];
+
+ vars.put("cache", args.ca[mix]);
+ vars.put("value", args.value[0]);
+ result = cacheSetValue.evaluate(jc);
+ assertEquals(cacheSetValue.toString(), args.value[0], result);
+
+ result = cacheGetValue.evaluate(jc);
+ assertEquals(cacheGetValue.toString(), args.value[0], result);
+
+ }
+
+ return Integer.valueOf(loops);
+ }
+ }
+
+ /**
+ * A task to check list assignment.
+ */
+ public static class AssignListTask extends Task {
+ public AssignListTask(int loops) {
+ super(loops);
+ }
+
+ public Integer call() throws Exception {
+ return runAssignList();
+ }
+ /** The actual test function. */
+ private Integer runAssignList() {
+ args.value = new Object[]{"foo"};
+ java.util.ArrayList<String> c1 = new java.util.ArrayList<String>(2);
+ c1.add("foo");
+ c1.add("bar");
+ args.ca = new Object[]{
+ new String[]{"one", "two"},
+ c1
+ };
+
+ Expression cacheGetValue = jexl.createExpression("cache.0");
+ Expression cacheSetValue = jexl.createExpression("cache[0] = value");
+ Object result;
+
+ for (int l = 0; l < loops; ++l) {
+ int px = (int) Thread.currentThread().getId();
+ int mix = MIX[(l + px) % MIX.length] % args.ca.length;
+
+ vars.put("cache", args.ca[mix]);
+ vars.put("value", args.value[0]);
+ result = cacheSetValue.evaluate(jc);
+ assertEquals(cacheSetValue.toString(), args.value[0], result);
+
+ result = cacheGetValue.evaluate(jc);
+ assertEquals(cacheGetValue.toString(), args.value[0], result);
+ }
+
+ return Integer.valueOf(loops);
+ }
+ }
+
+
+ public void testNullAssignNoCache() throws Exception {
+ runThreaded(AssignNullTask.class, LOOPS, false);
+ }
+
+ public void testNullAssignCache() throws Exception {
+ runThreaded(AssignNullTask.class, LOOPS, true);
+ }
+
+ public void testAssignNoCache() throws Exception {
+ runThreaded(AssignTask.class, LOOPS, false);
+ }
+
+ public void testAssignCache() throws Exception {
+ runThreaded(AssignTask.class, LOOPS, true);
+ }
+
+ public void testAssignBooleanNoCache() throws Exception {
+ runThreaded(AssignBooleanTask.class, LOOPS, false);
+ }
+
+ public void testAssignBooleanCache() throws Exception {
+ runThreaded(AssignBooleanTask.class, LOOPS, true);
+ }
+
+ public void testAssignListNoCache() throws Exception {
+ runThreaded(AssignListTask.class, LOOPS, false);
+ }
+
+ public void testAssignListCache() throws Exception {
+ runThreaded(AssignListTask.class, LOOPS, true);
+ }
+
+ /**
+ * A task to check method calls.
+ */
+ public static class ComputeTask extends Task {
+ public ComputeTask(int loops) {
+ super(loops);
+ }
+
+ public Integer call() throws Exception {
+ args.ca = new Object[]{args.c0, args.c1, args.c2};
+ args.value = new Object[]{new Integer(2), "quux"};
+ //jexl.setDebug(true);
+ Expression compute2 = jexl.createExpression("cache.compute(a0, a1)");
+ Expression compute1 = jexl.createExpression("cache.compute(a0)");
+ Expression compute1null = jexl.createExpression("cache.compute(a0)");
+ Expression ambiguous = jexl.createExpression("cache.ambiguous(a0, a1)");
+ //jexl.setDebug(false);
+
+ Object result = null;
+ String expected = null;
+ for (int l = 0; l < loops; ++l) {
+ int mix = MIX[l % MIX.length] % args.ca.length;
+ Object value = args.value[l % args.value.length];
+
+ vars.put("cache", args.ca[mix]);
+ if (value instanceof String) {
+ vars.put("a0", "S0");
+ vars.put("a1", "S1");
+ expected = "Cached" + mix + "@s#S0,s#S1";
+ } else if (value instanceof Integer) {
+ vars.put("a0", Integer.valueOf(7));
+ vars.put("a1", Integer.valueOf(9));
+ expected = "Cached" + mix + "@i#7,i#9";
+ } else {
+ fail("unexpected value type");
+ }
+ result = compute2.evaluate(jc);
+ assertEquals(compute2.toString(), expected, result);
+
+ if (value instanceof Integer) {
+ try {
+ vars.put("a0", Short.valueOf((short) 17));
+ vars.put("a1", Short.valueOf((short) 19));
+ result = ambiguous.evaluate(jc);
+ fail("should have thrown an exception");
+ } catch (JexlException xany) {
+ // throws due to ambiguous exception
+ }
+ }
+
+ if (value instanceof String) {
+ vars.put("a0", "X0");
+ expected = "Cached" + mix + "@s#X0";
+ } else if (value instanceof Integer) {
+ vars.put("a0", Integer.valueOf(5));
+ expected = "Cached" + mix + "@i#5";
+ } else {
+ fail("unexpected value type");
+ }
+ result = compute1.evaluate(jc);
+ assertEquals(compute1.toString(), expected, result);
+
+ try {
+ vars.put("a0", null);
+ result = compute1null.evaluate(jc);
+ fail("should have thrown an exception");
+ } catch (JexlException xany) {
+ // throws due to ambiguous exception
+ String sany = xany.getMessage();
+ String tname = getClass().getName();
+ if (!sany.startsWith(tname)) {
+ fail("debug mode should carry caller information, "
+ + sany + ", "
+ + tname);
+ }
+ }
+ }
+ return Integer.valueOf(loops);
+ }
+ }
+
+ public void testComputeNoCache() throws Exception {
+ try {
+ jexl.setDebug(true);
+ runThreaded(ComputeTask.class, LOOPS, false);
+ } finally {
+ jexl.setDebug(false);
+ }
+ }
+
+ public void testComputeCache() throws Exception {
+ try {
+ jexl.setDebug(true);
+ runThreaded(ComputeTask.class, LOOPS, true);
+ } finally {
+ jexl.setDebug(false);
+ }
+ }
+
+ /**
+ * The remaining tests exercise the namespaced functions; not MT.
+ * @param x
+ * @param loops
+ * @param cache
+ * @throws Exception
+ */
+ void doCOMPUTE(TestCacheArguments x, int loops, boolean cache) throws Exception {
+ if (loops == 0) {
+ loops = MIX.length;
+ }
+ if (cache) {
+ jexl.setCache(32);
+ } else {
+ jexl.setCache(0);
+ }
+ Map<String, Object> vars = new HashMap<String, Object>();
+ JexlContext jc = new MapContext(vars);
+ java.util.Map<String, Object> funcs = new java.util.HashMap<String, Object>();
+ jexl.setFunctions(funcs);
+ Expression compute2 = jexl.createExpression("cached:COMPUTE(a0, a1)");
+ Expression compute1 = jexl.createExpression("cached:COMPUTE(a0)");
+ Object result = null;
+ String expected = null;
+ for (int l = 0; l < loops; ++l) {
+ int mix = MIX[l % MIX.length] % x.ca.length;
+ Object value = x.value[l % x.value.length];
+
+ funcs.put("cached", x.ca[mix]);
+ if (value instanceof String) {
+ vars.put("a0", "S0");
+ vars.put("a1", "S1");
+ expected = "CACHED@s#S0,s#S1";
+ } else if (value instanceof Integer) {
+ vars.put("a0", Integer.valueOf(7));
+ vars.put("a1", Integer.valueOf(9));
+ expected = "CACHED@i#7,i#9";
+ } else {
+ fail("unexpected value type");
+ }
+ result = compute2.evaluate(jc);
+ assertEquals(compute2.toString(), expected, result);
+
+ if (value instanceof String) {
+ vars.put("a0", "X0");
+ expected = "CACHED@s#X0";
+ } else if (value instanceof Integer) {
+ vars.put("a0", Integer.valueOf(5));
+ expected = "CACHED@i#5";
+ } else {
+ fail("unexpected value type");
+ }
+ result = compute1.evaluate(jc);
+ assertEquals(compute1.toString(), expected, result);
+ }
+ }
+
+ public void testCOMPUTENoCache() throws Exception {
+ TestCacheArguments args = new TestCacheArguments();
+ args.ca = new Object[]{
+ Cached.class, Cached1.class, Cached2.class
+ };
+ args.value = new Object[]{new Integer(2), "quux"};
+ doCOMPUTE(args, LOOPS, false);
+ }
+
+ public void testCOMPUTECache() throws Exception {
+ TestCacheArguments args = new TestCacheArguments();
+ args.ca = new Object[]{
+ Cached.class, Cached1.class, Cached2.class
+ };
+ args.value = new Object[]{new Integer(2), "quux"};
+ doCOMPUTE(args, LOOPS, true);
+ }
+}
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/ClassCreator.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/ClassCreator.java
new file mode 100644
index 0000000..20959ad
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/ClassCreator.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+/**
+ * Helper class to test GC / reference interactions.
+ * Dynamically creates a class by compiling generated source Java code and
+ * load it through a dedicated class loader.
+ */
+public class ClassCreator {
+ private final JexlEngine jexl;
+ private final File base;
+ private File packageDir = null;
+ private int seed = 0;
+ private String className = null;
+ private String sourceName = null;
+ private ClassLoader loader = null;
+ public static final boolean canRun = comSunToolsJavacMain();
+ /**
+ * Check if we can invoke Sun's java compiler.
+ * @return true if it is possible, false otherwise
+ */
+ private static boolean comSunToolsJavacMain() {
+ try {
+ Class<?> javac = ClassCreatorTest.class.getClassLoader().loadClass("com.sun.tools.javac.Main");
+ return javac != null;
+ } catch (Exception xany) {
+ return false;
+ }
+ }
+
+ public ClassCreator(JexlEngine theJexl, File theBase) throws Exception {
+ jexl = theJexl;
+ base = theBase;
+ }
+
+
+ public void clear() {
+ seed = 0;
+ packageDir = null;
+ className = null;
+ sourceName = null;
+ packageDir = null;
+ loader = null;
+ }
+
+ public void setSeed(int s) {
+ seed = s;
+ className = "foo" + s;
+ sourceName = className + ".java";
+ packageDir = new File(base, seed + "/org/apache/commons/jexl2/generated");
+ packageDir.mkdirs();
+ loader = null;
+ }
+
+ public String getClassName() {
+ return "org.apache.commons.jexl2.generated." + className;
+ }
+
+ public Class<?> getClassInstance() throws Exception {
+ return getClassLoader().loadClass("org.apache.commons.jexl2.generated." + className);
+ }
+
+ public ClassLoader getClassLoader() throws Exception {
+ if (loader == null) {
+ URL classpath = (new File(base, Integer.toString(seed))).toURI().toURL();
+ loader = new URLClassLoader(new URL[]{classpath}, null);
+ }
+ return loader;
+ }
+
+ public Class<?> createClass() throws Exception {
+ // generate, compile & validate
+ generate();
+ Class<?> clazz = compile();
+ if (clazz == null) {
+ throw new Exception("failed to compile foo" + seed);
+ }
+ Object v = validate(clazz);
+ if (v instanceof Integer && ((Integer) v).intValue() == seed) {
+ return clazz;
+ }
+ throw new Exception("failed to validate foo" + seed);
+ }
+
+ void generate() throws Exception {
+ FileWriter aWriter = new FileWriter(new File(packageDir, sourceName), false);
+ aWriter.write("package org.apache.commons.jexl2.generated;");
+ aWriter.write("public class " + className + "{\n");
+ aWriter.write("private int value =");
+ aWriter.write(Integer.toString(seed));
+ aWriter.write(";\n");
+ aWriter.write(" public void setValue(int v) {");
+ aWriter.write(" value = v;");
+ aWriter.write(" }\n");
+ aWriter.write(" public int getValue() {");
+ aWriter.write(" return value;");
+ aWriter.write(" }\n");
+ aWriter.write(" }\n");
+ aWriter.flush();
+ aWriter.close();
+ }
+
+ Class<?> compile() throws Exception {
+ String source = packageDir.getPath() + "/" + sourceName;
+ Class<?> javac = getClassLoader().loadClass("com.sun.tools.javac.Main");
+ if (javac == null) {
+ return null;
+ }
+ Integer r = (Integer) jexl.invokeMethod(javac, "compile", source);
+ if (r.intValue() >= 0) {
+ return getClassLoader().loadClass("org.apache.commons.jexl2.generated." + className);
+ }
+ return null;
+ }
+
+ Object validate(Class<?> clazz) throws Exception {
+ Class<?> params[] = {};
+ Object paramsObj[] = {};
+ Object iClass = clazz.newInstance();
+ Method thisMethod = clazz.getDeclaredMethod("getValue", params);
+ return thisMethod.invoke(iClass, paramsObj);
+ }
+}
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/ClassCreatorTest.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/ClassCreatorTest.java
new file mode 100644
index 0000000..e786b1c
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/ClassCreatorTest.java
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+
+import java.io.File;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Basic check on automated class creation
+ */
+public class ClassCreatorTest extends JexlTestCase {
+ static final Log logger = LogFactory.getLog(JexlTestCase.class);
+ static final int LOOPS = 8;
+ private File base = null;
+ private JexlEngine jexl = null;
+
+ @Override
+ public void setUp() throws Exception {
+ base = new File(System.getProperty("java.io.tmpdir") + File.pathSeparator + "jexl" + System.currentTimeMillis());
+ jexl = new JexlEngine();
+ jexl.setCache(512);
+
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ deleteDirectory(base);
+ }
+
+ private void deleteDirectory(File dir) {
+ if (dir.isDirectory()) {
+ for (File file : dir.listFiles()) {
+ if (file.isFile()) {
+ file.delete();
+ }
+ }
+ }
+ dir.delete();
+ }
+
+ // A space hog class
+ static final int MEGA = 1024 * 1024;
+ public class BigObject {
+ @SuppressWarnings("unused")
+ private final byte[] space = new byte[MEGA];
+ private final int id;
+
+ public BigObject(int id) {
+ this.id = id;
+ }
+
+ public int getId() {
+ return id;
+ }
+ }
+
+ // A soft reference on class
+ static final class ClassReference extends WeakReference<Class<?>> {
+ ClassReference(Class<?> clazz, ReferenceQueue<Object> queue) {
+ super(clazz, queue);
+ }
+ }
+ // A weak reference on instance
+ static final class InstanceReference extends SoftReference<Object> {
+ InstanceReference(Object obj, ReferenceQueue<Object> queue) {
+ super(obj, queue);
+ }
+ }
+
+ public void testOne() throws Exception {
+ // abort test if class creator can not run
+ if (!ClassCreator.canRun) {
+ return;
+ }
+ ClassCreator cctor = new ClassCreator(jexl, base);
+ cctor.setSeed(1);
+ Class<?> foo1 = cctor.createClass();
+ assertEquals("foo1", foo1.getSimpleName());
+ cctor.clear();
+ }
+
+ public void testMany() throws Exception {
+ // abort test if class creator can not run
+ if (!ClassCreator.canRun) {
+ return;
+ }
+ int pass = 0;
+ int gced = -1;
+ ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
+ List<Reference<?>> stuff = new ArrayList<Reference<?>>();
+ // keeping a reference on methods prevent classes from being GCed
+// List<Object> mm = new ArrayList<Object>();
+ Expression expr = jexl.createExpression("foo.value");
+ Expression newx = jexl.createExpression("foo = new(clazz)");
+ JexlContext context = new MapContext();
+
+ ClassCreator cctor = new ClassCreator(jexl, base);
+ for (int i = 0; i < LOOPS && gced < 0; ++i) {
+ cctor.setSeed(i);
+ Class<?> clazz;
+ if (pass == 0) {
+ clazz = cctor.createClass();
+ } else {
+ clazz = cctor.getClassInstance();
+ if (clazz == null) {
+ assertEquals(i, gced);
+ break;
+ }
+ }
+ // this code verifies the assumption that holding a strong reference to a method prevents
+ // its owning class from being GCed
+// Method m = clazz.getDeclaredMethod("getValue", new Class<?>[0]);
+// mm.add(m);
+ // we should not be able to create foox since it is unknown to the Jexl classloader
+ context.set("clazz", cctor.getClassName());
+ context.set("foo", null);
+ Object z = newx.evaluate(context);
+ assertNull(z);
+ // check with the class itself
+ context.set("clazz", clazz);
+ z = newx.evaluate(context);
+ assertNotNull(clazz + ": class " + i + " could not be instantiated on pass " + pass, z);
+ assertEquals(new Integer(i), expr.evaluate(context));
+ // with the proper class loader, attempt to create an instance from the class name
+ jexl.setClassLoader(cctor.getClassLoader());
+ z = newx.evaluate(context);
+ assertTrue(z.getClass().equals(clazz));
+ assertEquals(new Integer(i), expr.evaluate(context));
+ cctor.clear();
+ jexl.setClassLoader(null);
+
+ // on pass 0, attempt to force GC to run and collect generated classes
+ if (pass == 0) {
+ // add a weak reference on the class
+ stuff.add(new ClassReference(clazz, queue));
+ // add a soft reference on an instance
+ stuff.add(new InstanceReference(clazz.newInstance(), queue));
+
+ // attempt to force GC:
+ // while we still have a MB free, create & store big objects
+ for (int b = 0; b < 64 && Runtime.getRuntime().freeMemory() > MEGA; ++b) {
+ BigObject big = new BigObject(b);
+ stuff.add(new InstanceReference(big, queue));
+ }
+ // hint it...
+ System.gc();
+ // let's see if some weak refs got collected
+ boolean qr = false;
+ while (queue.poll() != null) {
+ Reference<?> ref = queue.remove(1);
+ if (ref instanceof ClassReference) {
+ gced = i;
+ qr = true;
+ }
+ }
+ if (qr) {
+ //logger.warn("may have GCed class around " + i);
+ pass = 1;
+ i = 0;
+ }
+ }
+ }
+
+ if (gced < 0) {
+ logger.warn("unable to force GC");
+ //assertTrue(gced > 0);
+ }
+ }
+
+}
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/Foo.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/Foo.java
new file mode 100644
index 0000000..158c708
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/Foo.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A simple bean used for testing purposes
+ *
+ * @since 1.0
+ * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a>
+ * @version $Revision$
+ */
+public class Foo {
+
+ private boolean beenModified = false;
+ private String property1 = "some value";
+ public Foo() {}
+ public class Cheezy {
+ public Iterator<String> iterator() {
+ return getCheeseList().iterator();
+ }
+ }
+
+ public String bar()
+ {
+ return JexlTest.METHOD_STRING;
+ }
+
+ public String getBar()
+ {
+ return JexlTest.GET_METHOD_STRING;
+ }
+
+ public Foo getInnerFoo()
+ {
+ return new Foo();
+ }
+
+ public String get(String arg)
+ {
+ return "Repeat : " + arg;
+ }
+
+ public String convertBoolean(boolean b)
+ {
+ return "Boolean : " + b;
+ }
+
+ public int getCount() {
+ return 5;
+ }
+
+ public List<String> getCheeseList()
+ {
+ ArrayList<String> answer = new ArrayList<String>();
+ answer.add("cheddar");
+ answer.add("edam");
+ answer.add("brie");
+ return answer;
+ }
+
+ public Cheezy getCheezy()
+ {
+ return new Cheezy();
+ }
+
+ public String[] getArray()
+ {
+ return ArrayAccessTest.GET_METHOD_ARRAY;
+ }
+
+ public String[][] getArray2()
+ {
+ return ArrayAccessTest.GET_METHOD_ARRAY2;
+ }
+
+ public boolean isSimple()
+ {
+ return true;
+ }
+
+ public int square(int value)
+ {
+ return value * value;
+ }
+
+ public boolean getTrueAndModify()
+ {
+ beenModified = true;
+ return true;
+ }
+
+ public boolean getModified()
+ {
+ return beenModified;
+ }
+
+
+ public int getSize()
+ {
+ return 22;
+ }
+
+ public String getProperty1() {
+ return property1;
+ }
+
+ public void setProperty1(String newValue) {
+ property1 = newValue;
+ }
+}
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/ForEachTest.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/ForEachTest.java
new file mode 100644
index 0000000..42b28ef
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/ForEachTest.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+
+/**
+ * Tests for the foreach statement
+ * @author Dion Gillard
+ * @since 1.1
+ */
+public class ForEachTest extends JexlTestCase {
+
+ /** create a named test */
+ public ForEachTest(String name) {
+ super(name);
+ }
+
+ public void testForEachWithEmptyStatement() throws Exception {
+ Expression e = JEXL.createExpression("for(item : list) ;");
+ JexlContext jc = new MapContext();
+
+ Object o = e.evaluate(jc);
+ assertNull("Result is not null", o);
+ }
+
+ public void testForEachWithEmptyList() throws Exception {
+ Expression e = JEXL.createExpression("for(item : list) 1+1");
+ JexlContext jc = new MapContext();
+
+ Object o = e.evaluate(jc);
+ assertNull("Result is not null", o);
+ }
+
+ public void testForEachWithArray() throws Exception {
+ Expression e = JEXL.createExpression("for(item : list) item");
+ JexlContext jc = new MapContext();
+ jc.set("list", new Object[] {"Hello", "World"});
+ Object o = e.evaluate(jc);
+ assertEquals("Result is not last evaluated expression", "World", o);
+ }
+
+ public void testForEachWithCollection() throws Exception {
+ Expression e = JEXL.createExpression("for(item : list) item");
+ JexlContext jc = new MapContext();
+ jc.set("list", Arrays.asList(new Object[] {"Hello", "World"}));
+ Object o = e.evaluate(jc);
+ assertEquals("Result is not last evaluated expression", "World", o);
+ }
+
+ public void testForEachWithEnumeration() throws Exception {
+ Expression e = JEXL.createExpression("for(item : list) item");
+ JexlContext jc = new MapContext();
+ jc.set("list", new StringTokenizer("Hello,World", ","));
+ Object o = e.evaluate(jc);
+ assertEquals("Result is not last evaluated expression", "World", o);
+ }
+
+ public void testForEachWithIterator() throws Exception {
+ Expression e = JEXL.createExpression("for(item : list) item");
+ JexlContext jc = new MapContext();
+ jc.set("list", Arrays.asList(new Object[] {"Hello", "World"}).iterator());
+ Object o = e.evaluate(jc);
+ assertEquals("Result is not last evaluated expression", "World", o);
+ }
+
+ public void testForEachWithMap() throws Exception {
+ Expression e = JEXL.createExpression("for(item : list) item");
+ JexlContext jc = new MapContext();
+ Map<?, ?> map = System.getProperties();
+ String lastProperty = (String) new ArrayList<Object>(map.values()).get(System.getProperties().size() - 1);
+ jc.set("list", map);
+ Object o = e.evaluate(jc);
+ assertEquals("Result is not last evaluated expression", lastProperty, o);
+ }
+
+ public void testForEachWithBlock() throws Exception {
+ Expression exs0 = JEXL.createExpression("for(in : list) { x = x + in; }");
+ Expression exs1 = JEXL.createExpression("foreach(item in list) { x = x + item; }");
+ Expression []exs = { exs0, exs1 };
+ JexlContext jc = new MapContext();
+ jc.set("list", new Object[] {"2", "3"});
+ for(int ex = 0; ex < exs.length; ++ex) {
+ jc.set("x", new Integer(1));
+ Object o = exs[ex].evaluate(jc);
+ assertEquals("Result is wrong", new Integer(6), o);
+ assertEquals("x is wrong", new Integer(6), jc.get("x"));
+ }
+ }
+
+ public void testForEachWithListExpression() throws Exception {
+ Expression e = JEXL.createExpression("for(item : list.keySet()) item");
+ JexlContext jc = new MapContext();
+ Map<?, ?> map = System.getProperties();
+ String lastKey = (String) new ArrayList<Object>(map.keySet()).get(System.getProperties().size() - 1);
+ jc.set("list", map);
+ Object o = e.evaluate(jc);
+ assertEquals("Result is not last evaluated expression", lastKey, o);
+ }
+
+ public void testForEachWithProperty() throws Exception {
+ Expression e = JEXL.createExpression("for(item : list.cheeseList) item");
+ JexlContext jc = new MapContext();
+ jc.set("list", new Foo());
+ Object o = e.evaluate(jc);
+ assertEquals("Result is not last evaluated expression", "brie", o);
+ }
+
+ public void testForEachWithIteratorMethod() throws Exception {
+ Expression e = JEXL.createExpression("for(item : list.cheezy) item");
+ JexlContext jc = new MapContext();
+ jc.set("list", new Foo());
+ Object o = e.evaluate(jc);
+ assertEquals("Result is not last evaluated expression", "brie", o);
+ }
+}
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/IfTest.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/IfTest.java
new file mode 100644
index 0000000..94eb37d
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/IfTest.java
@@ -0,0 +1,279 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2;
+
+
+/**
+ * Test cases for the if statement.
+ *
+ * @author Dion Gillard
+ * @since 1.1
+ */
+public class IfTest extends JexlTestCase {
+
+ public IfTest(String testName) {
+ super(testName);
+ }
+
+ /**
+ * Make sure if true executes the true statement
+ *
+ * @throws Exception on any error
+ */
+ public void testSimpleIfTrue() throws Exception {
+ Expression e = JEXL.createExpression("if (true) 1");
+ JexlContext jc = new MapContext();
+
+ Object o = e.evaluate(jc);
+ assertEquals("Result is not 1", new Integer(1), o);
+ }
+
+ /**
+ * Make sure if false doesn't execute the true statement
+ *
+ * @throws Exception on any error
+ */
+ public void testSimpleIfFalse() throws Exception {
+ Expression e = JEXL.createExpression("if (false) 1");
+ JexlContext jc = new MapContext();
+
+ Object o = e.evaluate(jc);
+ assertNull("Return value is not empty", o);
+ }
+
+ /**
+ * Make sure if false executes the false statement
+ *
+ * @throws Exception on any error
+ */
+ public void testSimpleElse() throws Exception {
+ Expression e = JEXL
+ .createExpression("if (false) 1 else 2;");
+ JexlContext jc = new MapContext();
+
+ Object o = e.evaluate(jc);
+ assertEquals("Result is not 2", new Integer(2), o);
+ }
+
+ /**
+ * Test the if statement handles blocks correctly
+ *
+ * @throws Exception on any error
+ */
+ public void testBlockIfTrue() throws Exception {
+ Expression e = JEXL
+ .createExpression("if (true) { 'hello'; }");
+ JexlContext jc = new MapContext();
+
+ Object o = e.evaluate(jc);
+ assertEquals("Result is wrong", "hello", o);
+ }
+
+ /**
+ * Test the if statement handles blocks in the else statement correctly
+ *
+ * @throws Exception on any error
+ */
+ public void testBlockElse() throws Exception {
+ Expression e = JEXL
+ .createExpression("if (false) {1} else {2 ; 3}");
+ JexlContext jc = new MapContext();
+
+ Object o = e.evaluate(jc);
+ assertEquals("Result is wrong", new Integer(3), o);
+ }
+
+ /**
+ * Test the if statement evaluates expressions correctly
+ *
+ * @throws Exception on any error
+ */
+ public void testIfWithSimpleExpression() throws Exception {
+ Expression e = JEXL
+ .createExpression("if (x == 1) true;");
+ JexlContext jc = new MapContext();
+ jc.set("x", new Integer(1));
+
+ Object o = e.evaluate(jc);
+ assertEquals("Result is not true", Boolean.TRUE, o);
+ }
+
+ /**
+ * Test the if statement evaluates arithmetic expressions correctly
+ *
+ * @throws Exception on any error
+ */
+ public void testIfWithArithmeticExpression() throws Exception {
+ Expression e = JEXL
+ .createExpression("if ((x * 2) + 1 == 5) true;");
+ JexlContext jc = new MapContext();
+ jc.set("x", new Integer(2));
+
+ Object o = e.evaluate(jc);
+ assertEquals("Result is not true", Boolean.TRUE, o);
+ }
+
+ /**
+ * Test the if statement evaluates decimal arithmetic expressions correctly
+ *
+ * @throws Exception on any error
+ */
+ public void testIfWithDecimalArithmeticExpression() throws Exception {
+ Expression e = JEXL
+ .createExpression("if ((x * 2) == 5) true");
+ JexlContext jc = new MapContext();
+ jc.set("x", new Float(2.5f));
+
+ Object o = e.evaluate(jc);
+ assertEquals("Result is not true", Boolean.TRUE, o);
+ }
+
+ /**
+ * Test the if statement works with assignment
+ *
+ * @throws Exception on any error
+ */
+ public void testIfWithAssignment() throws Exception {
+ Expression e = JEXL
+ .createExpression("if ((x * 2) == 5) {y = 1} else {y = 2;}");
+ JexlContext jc = new MapContext();
+ jc.set("x", new Float(2.5f));
+
+ e.evaluate(jc);
+ Object result = jc.get("y");
+ assertEquals("y has the wrong value", new Integer(1), result);
+ }
+
+ /**
+ * Ternary operator condition undefined or null evaluates to false
+ * independantly of engine flags.
+ * @throws Exception
+ */
+ public void testTernary() throws Exception {
+ JexlEngine jexl = new JexlEngine();
+ jexl.setCache(64);
+ JexlContext jc = new MapContext();
+ Expression e = jexl.createExpression("x.y.z = foo ?'bar':'quux'");
+ Object o;
+
+ // undefined foo
+
+ for(int l = 0; l < 4; ++l) {
+ jexl.setLenient((l & 1) != 0);
+ jexl.setSilent((l & 2) != 0);
+ o = e.evaluate(jc);
+ assertEquals("Should be quux", "quux", o);
+ o = jc.get("x.y.z");
+ assertEquals("Should be quux", "quux", o);
+ }
+
+ jc.set("foo", null);
+
+ for(int l = 0; l < 4; ++l) {
+ jexl.setLenient((l & 1) != 0);
+ jexl.setSilent((l & 2) != 0);
+ o = e.evaluate(jc);
+ assertEquals("Should be quux", "quux", o);
+ o = jc.get("x.y.z");
+ assertEquals("Should be quux", "quux", o);
+ }
+
+ jc.set("foo",Boolean.FALSE);
+
+ for(int l = 0; l < 4; ++l) {
+ jexl.setLenient((l & 1) != 0);
+ jexl.setSilent((l & 2) != 0);
+ o = e.evaluate(jc);
+ assertEquals("Should be quux", "quux", o);
+ o = jc.get("x.y.z");
+ assertEquals("Should be quux", "quux", o);
+ }
+
+ jc.set("foo",Boolean.TRUE);
+
+ for(int l = 0; l < 4; ++l) {
+ jexl.setLenient((l & 1) != 0);
+ jexl.setSilent((l & 2) != 0);
+ o = e.evaluate(jc);
+ assertEquals("Should be bar", "bar", o);
+ o = jc.get("x.y.z");
+ assertEquals("Should be bar", "bar", o);
+ }
+
+ debuggerCheck(jexl);
+ }
+
+ /**
+ * Ternary operator condition undefined or null evaluates to false
+ * independantly of engine flags.
+ * @throws Exception
+ */
+ public void testTernaryShorthand() throws Exception {
+ JexlEngine jexl = new JexlEngine();
+ jexl.setCache(64);
+ JexlContext jc = new MapContext();
+ Expression e = JEXL.createExpression("x.y.z = foo?:'quux'");
+ Object o;
+
+ // undefined foo
+
+ for(int l = 0; l < 4; ++l) {
+ jexl.setLenient((l & 1) != 0);
+ jexl.setSilent((l & 2) != 0);
+ o = e.evaluate(jc);
+ assertEquals("Should be quux", "quux", o);
+ o = jc.get("x.y.z");
+ assertEquals("Should be quux", "quux", o);
+ }
+
+ jc.set("foo", null);
+
+ for(int l = 0; l < 4; ++l) {
+ jexl.setLenient((l & 1) != 0);
+ jexl.setSilent((l & 2) != 0);
+ o = e.evaluate(jc);
+ assertEquals("Should be quux", "quux", o);
+ o = jc.get("x.y.z");
+ assertEquals("Should be quux", "quux", o);
+ }
+
+ jc.set("foo", Boolean.FALSE);
+
+ for(int l = 0; l < 4; ++l) {
+ jexl.setLenient((l & 1) != 0);
+ jexl.setSilent((l & 2) != 0);
+ o = e.evaluate(jc);
+ assertEquals("Should be quux", "quux", o);
+ o = jc.get("x.y.z");
+ assertEquals("Should be quux", "quux", o);
+ }
+
+ jc.set("foo","bar");
+
+ for(int l = 0; l < 4; ++l) {
+ jexl.setLenient((l & 1) != 0);
+ jexl.setSilent((l & 2) != 0);
+ o = e.evaluate(jc);
+ assertEquals("Should be bar", "bar", o);
+ o = jc.get("x.y.z");
+ assertEquals("Should be bar", "bar", o);
+ }
+
+ debuggerCheck(jexl);
+ }
+}
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/IssuesTest.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/IssuesTest.java
new file mode 100644
index 0000000..9bdcdd4
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/IssuesTest.java
@@ -0,0 +1,335 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+import org.apache.commons.jexl2.introspection.Uberspect;
+import org.apache.commons.jexl2.internal.Introspector;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.commons.jexl2.introspection.UberspectImpl;
+
+/**
+ * Test cases for reported issues
+ */
+public class IssuesTest extends JexlTestCase {
+ @Override
+ public void setUp() throws Exception {
+ // ensure jul logging is only error to avoid warning in silent mode
+ //java.util.logging.Logger.getLogger(JexlEngine.class.getName()).setLevel(java.util.logging.Level.SEVERE);
+ }
+
+ // JEXL-49: blocks not parsed (fixed)
+ public void test49() throws Exception {
+ Map<String,Object> vars = new HashMap<String,Object>();
+ JexlContext ctxt = new MapContext(vars);
+ String stmt = "{a = 'b'; c = 'd';}";
+ Script expr = JEXL.createScript(stmt);
+ /* Object value = */ expr.execute(ctxt);
+ assertTrue("JEXL-49 is not fixed", vars.get("a").equals("b") && vars.get("c").equals("d"));
+ }
+
+ // JEXL-48: bad assignment detection
+ public static class Another {
+ public String name = "whatever";
+ private Boolean foo = Boolean.TRUE;
+
+ public Boolean foo() {
+ return foo;
+ }
+
+ public int goo() {
+ return 100;
+ }
+ }
+
+ public static class Foo {
+ private Another inner;
+
+ Foo() {
+ inner = new Another();
+ }
+
+ public Another getInner() {
+ return inner;
+ }
+ }
+
+ public void test48() throws Exception {
+ JexlEngine jexl = new JexlEngine();
+ // ensure errors will throw
+ jexl.setSilent(false);
+ String jexlExp = "(foo.getInner().foo() eq true) and (foo.getInner().goo() = (foo.getInner().goo()+1-1))";
+ Expression e = jexl.createExpression(jexlExp);
+ JexlContext jc = new MapContext();
+ jc.set("foo", new Foo());
+
+ try {
+ /* Object o = */ e.evaluate(jc);
+ fail("Should have failed due to invalid assignment");
+ } catch (JexlException xjexl) {
+ // expected
+ }
+ }
+
+ // JEXL-47: C style comments (single & multi line) (fixed in Parser.jjt)
+ // JEXL-44: comments dont allow double quotes (fixed in Parser.jjt)
+ public void test47() throws Exception {
+ JexlEngine jexl = new JexlEngine();
+ // ensure errors will throw
+ jexl.setSilent(false);
+ JexlContext ctxt = new MapContext();
+
+ Expression expr = jexl.createExpression("true//false\n");
+ Object value = expr.evaluate(ctxt);
+ assertTrue("should be true", ((Boolean) value).booleanValue());
+
+ expr = jexl.createExpression("/*true*/false");
+ value = expr.evaluate(ctxt);
+ assertFalse("should be false", ((Boolean) value).booleanValue());
+
+ expr = jexl.createExpression("/*\"true\"*/false");
+ value = expr.evaluate(ctxt);
+ assertFalse("should be false", ((Boolean) value).booleanValue());
+ }
+
+ // JEXL-42: NullPointerException evaluating an expression
+ // fixed in JexlArithmetic by allowing add operator to deal with string, null
+ public void test42() throws Exception {
+ JexlEngine jexl = new JexlEngine();
+ UnifiedJEXL uel = new UnifiedJEXL(jexl);
+ // ensure errors will throw
+ //jexl.setSilent(false);
+ JexlContext ctxt = new MapContext();
+ ctxt.set("ax", "ok");
+
+ UnifiedJEXL.Expression expr = uel.parse("${ax+(bx)}");
+ Object value = expr.evaluate(ctxt);
+ assertTrue("should be ok", "ok".equals(value));
+ }
+
+ // JEXL-40: failed to discover all methods (non public class implements public method)
+ // fixed in ClassMap by taking newer version of populateCache from Velocity
+ public static abstract class Base {
+ public abstract boolean foo();
+ }
+
+ class Derived extends Base {
+ @Override
+ public boolean foo() {
+ return true;
+ }
+ }
+
+ public void test40() throws Exception {
+ JexlEngine jexl = new JexlEngine();
+ // ensure errors will throw
+ jexl.setSilent(false);
+ JexlContext ctxt = new MapContext();
+ ctxt.set("derived", new Derived());
+
+ Expression expr = jexl.createExpression("derived.foo()");
+ Object value = expr.evaluate(ctxt);
+ assertTrue("should be true", ((Boolean) value).booleanValue());
+ }
+
+ // JEXL-52: can be implemented by deriving Interpreter.{g,s}etAttribute; later
+ public void test52base() throws Exception {
+ JexlEngine jexl = new JexlEngine();
+ Uberspect uber = jexl.getUberspect();
+ // most likely, call will be in an Interpreter, getUberspect
+ String[] names = ((Introspector) uber).getMethodNames(Another.class);
+ assertTrue("should find methods", names.length > 0);
+ int found = 0;
+ for (String name : names) {
+ if ("foo".equals(name) || "goo".equals(name)) {
+ found += 1;
+ }
+ }
+ assertTrue("should have foo & goo", found == 2);
+
+ names = ((UberspectImpl) uber).getFieldNames(Another.class);
+ assertTrue("should find fields", names.length > 0);
+ found = 0;
+ for (String name : names) {
+ if ("name".equals(name)) {
+ found += 1;
+ }
+ }
+ assertTrue("should have name", found == 1);
+ }
+
+ // JEXL-10/JEXL-11: variable checking, null operand is error
+ public void test11() throws Exception {
+ JexlEngine jexl = new JexlEngine();
+ // ensure errors will throw
+ jexl.setSilent(false);
+ jexl.setLenient(false);
+ JexlContext ctxt = new MapContext();
+ ctxt.set("a", null);
+
+ String[] exprs = {
+ //"10 + null",
+ //"a - 10",
+ //"b * 10",
+ "a % b"//,
+ //"1000 / a"
+ };
+ for (int e = 0; e < exprs.length; ++e) {
+ try {
+ Expression expr = jexl.createExpression(exprs[e]);
+ /* Object value = */ expr.evaluate(ctxt);
+ fail(exprs[e] + " : should have failed due to null argument");
+ } catch (JexlException xjexl) {
+ // expected
+ }
+ }
+ }
+
+ // JEXL-62
+ public void test62() throws Exception {
+ JexlContext ctxt;
+ JexlEngine jexl = new JexlEngine();
+ jexl.setSilent(true); // to avoid throwing JexlException on null method call
+
+ Script jscript;
+
+ ctxt = new MapContext();
+ jscript = jexl.createScript("dummy.hashCode()");
+ assertEquals(jscript.getText(), null, jscript.execute(ctxt)); // OK
+
+ ctxt.set("dummy", "abcd");
+ assertEquals(jscript.getText(), Integer.valueOf("abcd".hashCode()), jscript.execute(ctxt)); // OK
+
+ jscript = jexl.createScript("dummy.hashCode");
+ assertEquals(jscript.getText(), null, jscript.execute(ctxt)); // OK
+
+ Expression jexpr;
+
+ ctxt = new MapContext();
+ jexpr = jexl.createExpression("dummy.hashCode()");
+ assertEquals(jexpr.getExpression(), null, jexpr.evaluate(ctxt)); // OK
+
+ ctxt.set("dummy", "abcd");
+ assertEquals(jexpr.getExpression(), Integer.valueOf("abcd".hashCode()), jexpr.evaluate(ctxt)); // OK
+
+ jexpr = jexl.createExpression("dummy.hashCode");
+ assertEquals(jexpr.getExpression(), null, jexpr.evaluate(ctxt)); // OK
+ }
+
+ // JEXL-73
+ public void test73() throws Exception {
+ JexlContext ctxt = new MapContext();
+ JexlEngine jexl = new JexlEngine();
+ jexl.setSilent(false);
+ jexl.setLenient(false);
+ Expression e;
+ e = jexl.createExpression("c.e");
+ try {
+ /* Object o = */ e.evaluate(ctxt);
+ } catch (JexlException xjexl) {
+ String msg = xjexl.getMessage();
+ assertTrue(msg.indexOf("variable c.e") > 0);
+ }
+
+ ctxt.set("c", "{ 'a' : 3, 'b' : 5}");
+ ctxt.set("e", Integer.valueOf(2));
+ try {
+ /* Object o = */ e.evaluate(ctxt);
+ } catch (JexlException xjexl) {
+ String msg = xjexl.getMessage();
+ assertTrue(msg.indexOf("variable c.e") > 0);
+ }
+
+ }
+
+ // JEXL-87
+ public void test87() throws Exception {
+ JexlContext ctxt = new MapContext();
+ JexlEngine jexl = new JexlEngine();
+ jexl.setSilent(false);
+ jexl.setLenient(false);
+ Expression divide = jexl.createExpression("l / r");
+ Expression modulo = jexl.createExpression("l % r");
+
+ ctxt.set("l", java.math.BigInteger.valueOf(7));
+ ctxt.set("r", java.math.BigInteger.valueOf(2));
+ assertEquals("3", divide.evaluate(ctxt).toString());
+ assertEquals("1", modulo.evaluate(ctxt).toString());
+
+ ctxt.set("l", java.math.BigDecimal.valueOf(7));
+ ctxt.set("r", java.math.BigDecimal.valueOf(2));
+ assertEquals("3.5", divide.evaluate(ctxt).toString());
+ assertEquals("1", modulo.evaluate(ctxt).toString());
+ }
+
+ // JEXL-90
+ public void test90() throws Exception {
+ JexlContext ctxt = new MapContext();
+ JexlEngine jexl = new JexlEngine();
+ jexl.setSilent(false);
+ jexl.setLenient(false);
+ jexl.setCache(16);
+ // ';' is necessary between expressions
+ String[] fexprs = {
+ "a=3 b=4",
+ "while(a) while(a)",
+ "1 2",
+ "if (true) 2; 3 {}",
+ "while (x) 1 if (y) 2 3"
+ };
+ for (int f = 0; f < fexprs.length; ++f) {
+ try {
+ jexl.createScript(fexprs[f]);
+ fail(fexprs[f] + ": Should have failed in parse");
+ } catch (JexlException xany) {
+ // expected to fail in parse
+ }
+ }
+ // ';' is necessary between expressions and only expressions
+ String[] exprs = {
+ "if (x) {1} if (y) {2}",
+ "if (x) 1 if (y) 2",
+ "while (x) 1 if (y) 2 else 3",
+ "for(z : [3, 4, 5]) { z } y ? 2 : 1",
+ "for(z : [3, 4, 5]) { z } if (y) 2 else 1"
+ };
+ ctxt.set("x", Boolean.FALSE);
+ ctxt.set("y", Boolean.TRUE);
+ for (int e = 0; e < exprs.length; ++e) {
+ Script s = jexl.createScript(exprs[e]);
+ assertEquals(Integer.valueOf(2), s.execute(ctxt));
+ }
+ debuggerCheck(jexl);
+ }
+
+ // JEXL-44
+ public void test44() throws Exception {
+ JexlContext ctxt = new MapContext();
+ JexlEngine jexl = new JexlEngine();
+ jexl.setSilent(false);
+ jexl.setLenient(false);
+ Script script;
+ script = jexl.createScript("'hello world!'//commented");
+ assertEquals("hello world!", script.execute(ctxt));
+ script = jexl.createScript("'hello world!';//commented\n'bye...'");
+ assertEquals("bye...", script.execute(ctxt));
+ script = jexl.createScript("'hello world!'## commented");
+ assertEquals("hello world!", script.execute(ctxt));
+ script = jexl.createScript("'hello world!';## commented\n'bye...'");
+ assertEquals("bye...", script.execute(ctxt));
+ }
+
+}
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/Jexl.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/Jexl.java
new file mode 100644
index 0000000..0a662aa
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/Jexl.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2;
+
+import java.util.Map;
+
+/**
+ * @author Dion Gillard
+ * @since 1.0
+ * Command line interface for Jexl for use in testing
+ */
+public class Jexl {
+
+ public static void main(String[] args) {
+ final JexlEngine JEXL = new JexlEngine();
+ Map<Object,Object> m = System.getProperties();
+ // dummy context to get variables
+ JexlContext context = new MapContext();
+ for(Map.Entry<Object,Object> e : m.entrySet()) {
+ context.set(e.getKey().toString(), e.getValue());
+ }
+ try {
+ for (int i = 0; i < args.length; i++) {
+ Expression e = JEXL.createExpression(args[i]);
+ System.out.println("evaluate(" + args[i] + ") = '" + e.evaluate(context) + "'");
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/JexlTest.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/JexlTest.java
new file mode 100644
index 0000000..60fc601
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/JexlTest.java
@@ -0,0 +1,825 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+
+import java.io.StringReader;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.jexl2.parser.Parser;
+
+/**
+ * Simple testcases
+ *
+ * @since 1.0
+ * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class JexlTest extends JexlTestCase
+{
+ protected static final String METHOD_STRING = "Method string";
+ protected static final String GET_METHOD_STRING = "GetMethod string";
+
+ public JexlTest(String testName)
+ {
+ super(testName);
+ }
+
+ /**
+ * test a simple property expression
+ */
+ public void testProperty()
+ throws Exception
+ {
+ /*
+ * tests a simple property expression
+ */
+
+ Expression e = JEXL.createExpression("foo.bar");
+ JexlContext jc = new MapContext();
+
+ jc.set("foo", new Foo() );
+ Object o = e.evaluate(jc);
+
+ assertTrue("o not instanceof String", o instanceof String);
+ assertEquals("o incorrect", GET_METHOD_STRING, o);
+ }
+
+ public void testBoolean()
+ throws Exception
+ {
+ JexlContext jc = new MapContext();
+ jc.set("foo", new Foo() );
+ jc.set("a", Boolean.TRUE);
+ jc.set("b", Boolean.FALSE);
+
+ assertExpression(jc, "foo.convertBoolean(a==b)", "Boolean : false");
+ assertExpression(jc, "foo.convertBoolean(a==true)", "Boolean : true");
+ assertExpression(jc, "foo.convertBoolean(a==false)", "Boolean : false");
+ assertExpression(jc, "foo.convertBoolean(true==false)", "Boolean : false");
+ assertExpression(jc, "true eq false", Boolean.FALSE);
+ assertExpression(jc, "true ne false", Boolean.TRUE);
+ }
+
+ public void testStringLit()
+ throws Exception
+ {
+ /*
+ * tests a simple property expression
+ */
+ JexlContext jc = new MapContext();
+ jc.set("foo", new Foo() );
+ assertExpression(jc, "foo.get(\"woogie\")", "Repeat : woogie");
+ }
+
+ public void testExpression()
+ throws Exception
+ {
+ JexlContext jc = new MapContext();
+ jc.set("foo", new Foo() );
+ jc.set("a", Boolean.TRUE);
+ jc.set("b", Boolean.FALSE);
+ jc.set("num", new Integer(5));
+ jc.set("now", Calendar.getInstance().getTime());
+ GregorianCalendar gc = new GregorianCalendar(5000, 11, 20);
+ jc.set("now2", gc.getTime());
+ jc.set("bdec", new BigDecimal("7"));
+ jc.set("bint", new BigInteger("7"));
+
+ assertExpression(jc, "a == b", Boolean.FALSE);
+ assertExpression(jc, "a==true", Boolean.TRUE);
+ assertExpression(jc, "a==false", Boolean.FALSE);
+ assertExpression(jc, "true==false", Boolean.FALSE);
+
+ assertExpression(jc, "2 < 3", Boolean.TRUE);
+ assertExpression(jc, "num < 5", Boolean.FALSE);
+ assertExpression(jc, "num < num", Boolean.FALSE);
+ assertExpression(jc, "num < null", Boolean.FALSE);
+ assertExpression(jc, "num < 2.5", Boolean.FALSE);
+ assertExpression(jc, "now2 < now", Boolean.FALSE); // test comparable
+//
+ assertExpression(jc, "'6' <= '5'", Boolean.FALSE);
+ assertExpression(jc, "num <= 5", Boolean.TRUE);
+ assertExpression(jc, "num <= num", Boolean.TRUE);
+ assertExpression(jc, "num <= null", Boolean.FALSE);
+ assertExpression(jc, "num <= 2.5", Boolean.FALSE);
+ assertExpression(jc, "now2 <= now", Boolean.FALSE); // test comparable
+
+//
+ assertExpression(jc, "'6' >= '5'", Boolean.TRUE);
+ assertExpression(jc, "num >= 5", Boolean.TRUE);
+ assertExpression(jc, "num >= num", Boolean.TRUE);
+ assertExpression(jc, "num >= null", Boolean.FALSE);
+ assertExpression(jc, "num >= 2.5", Boolean.TRUE);
+ assertExpression(jc, "now2 >= now", Boolean.TRUE); // test comparable
+
+ assertExpression(jc, "'6' > '5'", Boolean.TRUE);
+ assertExpression(jc, "num > 4", Boolean.TRUE);
+ assertExpression(jc, "num > num", Boolean.FALSE);
+ assertExpression(jc, "num > null", Boolean.FALSE);
+ assertExpression(jc, "num > 2.5", Boolean.TRUE);
+ assertExpression(jc, "now2 > now", Boolean.TRUE); // test comparable
+
+ assertExpression(jc, "\"foo\" + \"bar\" == \"foobar\"", Boolean.TRUE);
+
+ assertExpression(jc, "bdec > num", Boolean.TRUE);
+ assertExpression(jc, "bdec >= num", Boolean.TRUE);
+ assertExpression(jc, "num <= bdec", Boolean.TRUE);
+ assertExpression(jc, "num < bdec", Boolean.TRUE);
+ assertExpression(jc, "bint > num", Boolean.TRUE);
+ assertExpression(jc, "bint == bdec", Boolean.TRUE);
+ assertExpression(jc, "bint >= num", Boolean.TRUE);
+ assertExpression(jc, "num <= bint", Boolean.TRUE);
+ assertExpression(jc, "num < bint", Boolean.TRUE);
+ }
+
+ public void testEmpty()
+ throws Exception
+ {
+ JexlContext jc = new MapContext();
+ jc.set("string", "");
+ jc.set("array", new Object[0]);
+ jc.set("map", new HashMap<Object, Object>());
+ jc.set("list", new ArrayList<Object>());
+ jc.set("set", (new HashMap<Object, Object>()).keySet());
+ jc.set("longstring", "thingthing");
+
+ /*
+ * I can't believe anyone thinks this is a syntax.. :)
+ */
+ assertExpression(jc, "empty nullthing", Boolean.TRUE);
+ assertExpression(jc, "empty string", Boolean.TRUE);
+ assertExpression(jc, "empty array", Boolean.TRUE);
+ assertExpression(jc, "empty map", Boolean.TRUE);
+ assertExpression(jc, "empty set", Boolean.TRUE);
+ assertExpression(jc, "empty list", Boolean.TRUE);
+ assertExpression(jc, "empty longstring", Boolean.FALSE);
+ assertExpression(jc, "not empty longstring", Boolean.TRUE);
+ }
+
+ public void testSize()
+ throws Exception
+ {
+ JexlContext jc = new MapContext();
+ jc.set("s", "five!");
+ jc.set("array", new Object[5]);
+
+ Map<String, Integer> map = new HashMap<String, Integer>();
+
+ map.put("1", new Integer(1));
+ map.put("2", new Integer(2));
+ map.put("3", new Integer(3));
+ map.put("4", new Integer(4));
+ map.put("5", new Integer(5));
+
+ jc.set("map", map);
+
+ List<String> list = new ArrayList<String>();
+
+ list.add("1");
+ list.add("2");
+ list.add("3");
+ list.add("4");
+ list.add("5");
+
+ jc.set("list", list);
+
+ // 30652 - support for set
+ Set<String> set = new HashSet<String>();
+ set.addAll(list);
+ set.add("1");
+
+ jc.set("set", set);
+
+ // support generic int size() method
+ BitSet bitset = new BitSet(5);
+ jc.set("bitset", bitset);
+
+ assertExpression(jc, "size(s)", new Integer(5));
+ assertExpression(jc, "size(array)", new Integer(5));
+ assertExpression(jc, "size(list)", new Integer(5));
+ assertExpression(jc, "size(map)", new Integer(5));
+ assertExpression(jc, "size(set)", new Integer(5));
+ assertExpression(jc, "size(bitset)", new Integer(64));
+ assertExpression(jc, "list.size()", new Integer(5));
+ assertExpression(jc, "map.size()", new Integer(5));
+ assertExpression(jc, "set.size()", new Integer(5));
+ assertExpression(jc, "bitset.size()", new Integer(64));
+
+ assertExpression(jc, "list.get(size(list) - 1)", "5");
+ assertExpression(jc, "list[size(list) - 1]", "5");
+ assertExpression(jc, "list.get(list.size() - 1)", "5");
+ }
+
+ public void testSizeAsProperty() throws Exception
+ {
+ JexlContext jc = new MapContext();
+ Map<String, Object> map = new HashMap<String, Object>();
+ map.put("size", "cheese");
+ jc.set("map", map);
+ jc.set("foo", new Foo());
+
+ assertExpression(jc, "map['size']", "cheese");
+// PR - unsure whether or not we should support map.size or force usage of the above 'escaped' version
+// assertExpression(jc, "map.size", "cheese");
+ assertExpression(jc, "foo.getSize()", new Integer(22));
+ // failing assertion for size property
+ //assertExpression(jc, "foo.size", new Integer(22));
+ }
+
+ /**
+ * test the new function e.g constructor invocation
+ */
+ public void testNew() throws Exception {
+ JexlContext jc = new MapContext();
+ jc.set("double", Double.class);
+ jc.set("foo", "org.apache.commons.jexl2.Foo");
+ Expression expr;
+ Object value;
+ expr = JEXL.createExpression("new(double, 1)");
+ value = expr.evaluate(jc);
+ assertEquals(expr.toString(), new Double(1.0), value);
+ expr = JEXL.createExpression("new('java.lang.Float', 100)");
+ value = expr.evaluate(jc);
+ assertEquals(expr.toString(), new Float(100.0), value);
+ expr = JEXL.createExpression("new(foo).quux");
+ value = expr.evaluate(jc);
+ assertEquals(expr.toString(), "Repeat : quux", value);
+ }
+
+ /**
+ * test some simple mathematical calculations
+ */
+ public void testCalculations()
+ throws Exception
+ {
+ JexlContext jc = new MapContext();
+
+ /*
+ * test to ensure new string cat works
+ */
+ jc.set("stringy", "thingy" );
+ assertExpression(jc, "stringy + 2", "thingy2");
+
+ /*
+ * test new null coersion
+ */
+ jc.set("imanull", null );
+ assertExpression(jc, "imanull + 2", new Integer(2));
+ assertExpression(jc, "imanull + imanull", new Integer(0));
+
+ /* test for bugzilla 31577 */
+ jc.set("n", new Integer(0));
+ assertExpression(jc, "n != null && n != 0", Boolean.FALSE);
+ }
+
+ /**
+ * test some simple conditions
+ */
+ public void testConditions()
+ throws Exception
+ {
+ JexlContext jc = new MapContext();
+ jc.set("foo", new Integer(2) );
+ jc.set("aFloat", new Float(1));
+ jc.set("aDouble", new Double(2));
+ jc.set("aChar", new Character('A'));
+ jc.set("aBool", Boolean.TRUE);
+ StringBuffer buffer = new StringBuffer("abc");
+ List<Object> list = new ArrayList<Object>();
+ List<Object> list2 = new LinkedList<Object>();
+ jc.set("aBuffer", buffer);
+ jc.set("aList", list);
+ jc.set("bList", list2);
+
+ assertExpression(jc, "foo == 2", Boolean.TRUE);
+ assertExpression(jc, "2 == 3", Boolean.FALSE);
+ assertExpression(jc, "3 == foo", Boolean.FALSE);
+ assertExpression(jc, "3 != foo", Boolean.TRUE);
+ assertExpression(jc, "foo != 2", Boolean.FALSE);
+ // test float and double equality
+ assertExpression(jc, "aFloat eq aDouble", Boolean.FALSE);
+ assertExpression(jc, "aFloat ne aDouble", Boolean.TRUE);
+ assertExpression(jc, "aFloat == aDouble", Boolean.FALSE);
+ assertExpression(jc, "aFloat != aDouble", Boolean.TRUE);
+ // test number and character equality
+ assertExpression(jc, "foo == aChar", Boolean.FALSE);
+ assertExpression(jc, "foo != aChar", Boolean.TRUE);
+ // test string and boolean
+ assertExpression(jc, "aBool == 'true'", Boolean.TRUE);
+ assertExpression(jc, "aBool == 'false'", Boolean.FALSE);
+ assertExpression(jc, "aBool != 'false'", Boolean.TRUE);
+ // test null and boolean
+ assertExpression(jc, "aBool == notThere", Boolean.FALSE);
+ assertExpression(jc, "aBool != notThere", Boolean.TRUE);
+ // anything and string as a string comparison
+ assertExpression(jc, "aBuffer == 'abc'", Boolean.TRUE);
+ assertExpression(jc, "aBuffer != 'abc'", Boolean.FALSE);
+ // arbitrary equals
+ assertExpression(jc, "aList == bList", Boolean.TRUE);
+ assertExpression(jc, "aList != bList", Boolean.FALSE);
+ }
+
+ /**
+ * test some simple conditions
+ */
+ public void testNotConditions()
+ throws Exception
+ {
+ JexlContext jc = new MapContext();
+
+ Foo foo = new Foo();
+ jc.set("x", Boolean.TRUE );
+ jc.set("foo", foo );
+ jc.set("bar", "true" );
+
+ assertExpression(jc, "!x", Boolean.FALSE);
+ assertExpression(jc, "x", Boolean.TRUE);
+ assertExpression(jc, "!bar", Boolean.FALSE);
+ assertExpression(jc, "!foo.isSimple()", Boolean.FALSE);
+ assertExpression(jc, "foo.isSimple()", Boolean.TRUE);
+ assertExpression(jc, "!foo.simple", Boolean.FALSE);
+ assertExpression(jc, "foo.simple", Boolean.TRUE);
+ assertExpression(jc, "foo.getCheeseList().size() == 3", Boolean.TRUE);
+ assertExpression(jc, "foo.cheeseList.size() == 3", Boolean.TRUE);
+
+ jc.set("string", "");
+ assertExpression(jc, "not empty string", Boolean.FALSE);
+ assertExpression(jc, "not(empty string)", Boolean.FALSE);
+ assertExpression(jc, "not empty(string)", Boolean.FALSE);
+ assertExpression(jc, "! empty string", Boolean.FALSE);
+ assertExpression(jc, "!(empty string)", Boolean.FALSE);
+ assertExpression(jc, "!empty(string)", Boolean.FALSE);
+
+ }
+
+
+ /**
+ * test some simple conditions
+ */
+ public void testNotConditionsWithDots()
+ throws Exception
+ {
+ JexlContext jc = new MapContext();
+
+ jc.set("x.a", Boolean.TRUE );
+ jc.set("x.b", Boolean.FALSE );
+
+ assertExpression(jc, "x.a", Boolean.TRUE);
+ assertExpression(jc, "!x.a", Boolean.FALSE);
+ assertExpression(jc, "!x.b", Boolean.TRUE);
+ }
+
+ /**
+ * test some simple conditions
+ */
+ public void testComparisons()
+ throws Exception
+ {
+ JexlContext jc = new MapContext();
+ jc.set("foo", "the quick and lazy fox" );
+
+ assertExpression(jc, "foo.indexOf('quick') > 0", Boolean.TRUE);
+ assertExpression(jc, "foo.indexOf('bar') >= 0", Boolean.FALSE);
+ assertExpression(jc, "foo.indexOf('bar') < 0", Boolean.TRUE);
+ }
+
+ /**
+ * test some null conditions
+ */
+ public void testNull()
+ throws Exception
+ {
+ JexlContext jc = new MapContext();
+ jc.set("bar", new Integer(2) );
+
+ assertExpression(jc, "empty foo", Boolean.TRUE);
+ assertExpression(jc, "bar == null", Boolean.FALSE);
+ assertExpression(jc, "foo == null", Boolean.TRUE);
+ assertExpression(jc, "bar != null", Boolean.TRUE);
+ assertExpression(jc, "foo != null", Boolean.FALSE);
+ assertExpression(jc, "empty(bar)", Boolean.FALSE);
+ assertExpression(jc, "empty(foo)", Boolean.TRUE);
+ }
+
+ /**
+ * test quoting in strings
+ */
+ public void testStringQuoting() throws Exception {
+ JexlContext jc = new MapContext();
+ assertExpression(jc, "'\"Hello\"'", "\"Hello\"");
+ assertExpression(jc, "\"I'm testing\"", "I'm testing");
+ }
+
+ /**
+ * test some blank strings
+ */
+ public void testBlankStrings()
+ throws Exception
+ {
+ JexlContext jc = new MapContext();
+ jc.set("bar", "" );
+
+ assertExpression(jc, "foo == ''", Boolean.FALSE);
+ assertExpression(jc, "bar == ''", Boolean.TRUE);
+ assertExpression(jc, "barnotexist == ''", Boolean.FALSE);
+ assertExpression(jc, "empty bar", Boolean.TRUE);
+ assertExpression(jc, "bar.length() == 0", Boolean.TRUE);
+ assertExpression(jc, "size(bar) == 0", Boolean.TRUE);
+ }
+
+ /**
+ * test some blank strings
+ */
+ public void testLogicExpressions()
+ throws Exception
+ {
+ JexlContext jc = new MapContext();
+ jc.set("foo", "abc" );
+ jc.set("bar", "def" );
+
+ assertExpression(jc, "foo == 'abc' || bar == 'abc'", Boolean.TRUE);
+ assertExpression(jc, "foo == 'abc' or bar == 'abc'", Boolean.TRUE);
+ assertExpression(jc, "foo == 'abc' && bar == 'abc'", Boolean.FALSE);
+ assertExpression(jc, "foo == 'abc' and bar == 'abc'", Boolean.FALSE);
+
+ assertExpression(jc, "foo == 'def' || bar == 'abc'", Boolean.FALSE);
+ assertExpression(jc, "foo == 'def' or bar == 'abc'", Boolean.FALSE);
+ assertExpression(jc, "foo == 'abc' && bar == 'def'", Boolean.TRUE);
+ assertExpression(jc, "foo == 'abc' and bar == 'def'", Boolean.TRUE);
+ }
+
+
+ /**
+ * test variables with underscore names
+ */
+ public void testVariableNames()
+ throws Exception
+ {
+ JexlContext jc = new MapContext();
+ jc.set("foo_bar", "123" );
+
+ assertExpression(jc, "foo_bar", "123");
+ }
+
+ /**
+ * test the use of dot notation to lookup map entries
+ */
+ public void testMapDot()
+ throws Exception
+ {
+ Map<String, String> foo = new HashMap<String, String>();
+ foo.put( "bar", "123" );
+
+ JexlContext jc = new MapContext();
+ jc.set("foo", foo );
+
+ assertExpression(jc, "foo.bar", "123");
+ }
+
+ /**
+ * Tests string literals
+ */
+ public void testStringLiterals()
+ throws Exception
+ {
+ JexlContext jc = new MapContext();
+ jc.set("foo", "bar" );
+
+ assertExpression(jc, "foo == \"bar\"", Boolean.TRUE);
+ assertExpression(jc, "foo == 'bar'", Boolean.TRUE);
+ }
+
+ /**
+ * test the use of an int based property
+ */
+ public void testIntProperty()
+ throws Exception
+ {
+ Foo foo = new Foo();
+
+ // lets check the square function first..
+ assertEquals(4, foo.square(2));
+ assertEquals(4, foo.square(-2));
+
+ JexlContext jc = new MapContext();
+ jc.set("foo", foo );
+
+ assertExpression(jc, "foo.count", new Integer(5));
+ assertExpression(jc, "foo.square(2)", new Integer(4));
+ assertExpression(jc, "foo.square(-2)", new Integer(4));
+ }
+
+ /**
+ * test the -1 comparison bug
+ */
+ public void testNegativeIntComparison()
+ throws Exception
+ {
+ JexlContext jc = new MapContext();
+ Foo foo = new Foo();
+ jc.set("foo", foo );
+
+ assertExpression(jc, "foo.count != -1", Boolean.TRUE);
+ assertExpression(jc, "foo.count == 5", Boolean.TRUE);
+ assertExpression(jc, "foo.count == -1", Boolean.FALSE);
+ }
+
+ /**
+ * Attempts to recreate bug http://jira.werken.com/ViewIssue.jspa?key=JELLY-8
+ */
+ public void testCharAtBug()
+ throws Exception
+ {
+ JexlContext jc = new MapContext();
+
+ jc.set("foo", "abcdef");
+
+ assertExpression(jc, "foo.substring(2,4)", "cd");
+ assertExpression(jc, "foo.charAt(2)", new Character('c'));
+ assertExpression(jc, "foo.charAt(-2)", null);
+
+ }
+
+ public void testEmptyDottedVariableName() throws Exception
+ {
+ JexlContext jc = new MapContext();
+
+ jc.set( "this.is.a.test", "");
+
+ assertExpression(jc, "empty(this.is.a.test)", Boolean.TRUE);
+ }
+
+ public void testEmptySubListOfMap() throws Exception
+ {
+ JexlContext jc = new MapContext();
+ Map<String, ArrayList<?>> m = new HashMap<String, ArrayList<?>>();
+ m.put("aList", new ArrayList<Object>());
+
+ jc.set( "aMap", m );
+
+ assertExpression( jc, "empty( aMap.aList )", Boolean.TRUE );
+ }
+
+ public void testCoercionWithComparisionOperators()
+ throws Exception
+ {
+ JexlContext jc = new MapContext();
+
+ assertExpression(jc, "'2' > 1", Boolean.TRUE);
+ assertExpression(jc, "'2' >= 1", Boolean.TRUE);
+ assertExpression(jc, "'2' >= 2", Boolean.TRUE);
+ assertExpression(jc, "'2' < 1", Boolean.FALSE);
+ assertExpression(jc, "'2' <= 1", Boolean.FALSE);
+ assertExpression(jc, "'2' <= 2", Boolean.TRUE);
+
+ assertExpression(jc, "2 > '1'", Boolean.TRUE);
+ assertExpression(jc, "2 >= '1'", Boolean.TRUE);
+ assertExpression(jc, "2 >= '2'", Boolean.TRUE);
+ assertExpression(jc, "2 < '1'", Boolean.FALSE);
+ assertExpression(jc, "2 <= '1'", Boolean.FALSE);
+ assertExpression(jc, "2 <= '2'", Boolean.TRUE);
+ }
+
+ /**
+ * Test that 'and' only evaluates the second item if needed
+ * @throws Exception if there are errors
+ */
+ public void testBooleanShortCircuitAnd() throws Exception
+ {
+ // handle false for the left arg of 'and'
+ Foo tester = new Foo();
+ JexlContext jc = new MapContext();
+ jc.set("first", Boolean.FALSE);
+ jc.set("foo", tester);
+ Expression expr = JEXL.createExpression("first and foo.trueAndModify");
+ expr.evaluate(jc);
+ assertTrue("Short circuit failure: rhs evaluated when lhs FALSE", !tester.getModified());
+ // handle true for the left arg of 'and'
+ tester = new Foo();
+ jc.set("first", Boolean.TRUE);
+ jc.set("foo", tester);
+ expr.evaluate(jc);
+ assertTrue("Short circuit failure: rhs not evaluated when lhs TRUE", tester.getModified());
+ }
+
+ /**
+ * Test that 'or' only evaluates the second item if needed
+ * @throws Exception if there are errors
+ */
+ public void testBooleanShortCircuitOr() throws Exception
+ {
+ // handle false for the left arg of 'or'
+ Foo tester = new Foo();
+ JexlContext jc = new MapContext();
+ jc.set("first", Boolean.FALSE);
+ jc.set("foo", tester);
+ Expression expr = JEXL.createExpression("first or foo.trueAndModify");
+ expr.evaluate(jc);
+ assertTrue("Short circuit failure: rhs not evaluated when lhs FALSE", tester.getModified());
+ // handle true for the left arg of 'or'
+ tester = new Foo();
+ jc.set("first", Boolean.TRUE);
+ jc.set("foo", tester);
+ expr.evaluate(jc);
+ assertTrue("Short circuit failure: rhs evaluated when lhs TRUE", !tester.getModified());
+ }
+
+ /**
+ * Simple test of '+' as a string concatenation operator
+ * @throws Exception
+ */
+ public void testStringConcatenation() throws Exception
+ {
+ JexlContext jc = new MapContext();
+ jc.set("first", "Hello");
+ jc.set("second", "World");
+ assertExpression(jc, "first + ' ' + second", "Hello World");
+ }
+
+ public void testToString() throws Exception {
+ String code = "abcd";
+ Expression expr = JEXL.createExpression(code);
+ assertEquals("Bad expression value", code, expr.toString());
+ }
+
+ /**
+ * Make sure bad syntax throws ParseException
+ * @throws Exception on errors
+ */
+ public void testBadParse() throws Exception
+ {
+ try
+ {
+ assertExpression(new MapContext(), "empty()", null);
+ fail("Bad expression didn't throw ParseException");
+ }
+ catch (JexlException pe)
+ {
+ // expected behaviour
+ }
+ }
+
+ /**
+ * Test the ## comment in a string
+ * @throws Exception
+ */
+ public void testComment() throws Exception
+ {
+ assertExpression(new MapContext(), "## double or nothing\n 1 + 1", Integer.valueOf("2"));
+ }
+
+ /**
+ * Test assignment.
+ * @throws Exception
+ */
+ public void testAssignment() throws Exception
+ {
+ JexlContext jc = new MapContext();
+ jc.set("aString", "Hello");
+ Foo foo = new Foo();
+ jc.set("foo", foo);
+ Parser parser = new Parser(new StringReader(";"));
+ parser.parse(new StringReader("aString = 'World';"), null);
+
+ assertExpression(jc, "hello = 'world'", "world");
+ assertEquals("hello variable not changed", "world", jc.get("hello"));
+ assertExpression(jc, "result = 1 + 1", new Integer(2));
+ assertEquals("result variable not changed", new Integer(2), jc.get("result"));
+ // todo: make sure properties can be assigned to, fall back to flat var if no property
+ // assertExpression(jc, "foo.property1 = '99'", "99");
+ // assertEquals("property not set", "99", foo.getProperty1());
+ }
+
+ public void testAntPropertiesWithMethods() throws Exception
+ {
+ JexlContext jc = new MapContext();
+ String value = "Stinky Cheese";
+ jc.set("maven.bob.food", value);
+ assertExpression(jc, "maven.bob.food.length()", new Integer(value.length()));
+ assertExpression(jc, "empty(maven.bob.food)", Boolean.FALSE);
+ assertExpression(jc, "size(maven.bob.food)", new Integer(value.length()));
+ assertExpression(jc, "maven.bob.food + ' is good'", value + " is good");
+
+ // DG: Note the following ant properties don't work
+// String version = "1.0.3";
+// jc.set("commons-logging", version);
+// assertExpression(jc, "commons-logging", version);
+ }
+
+ public void testUnicodeSupport() throws Exception
+ {
+ JexlContext jc = new MapContext();
+ assertExpression(jc, "myvar == 'Użytkownik'", Boolean.FALSE);
+ assertExpression(jc, "'c:\\some\\windows\\path'", "c:\\some\\windows\\path");
+ assertExpression(jc, "'foo\\u0020bar'", "foo\u0020bar");
+ assertExpression(jc, "'foo\\u0020\\u0020bar'", "foo\u0020\u0020bar");
+ assertExpression(jc, "'\\u0020foobar\\u0020'", "\u0020foobar\u0020");
+ }
+
+ public static final class Duck {
+ int user = 10;
+ @SuppressWarnings("boxing")
+ public Integer get(String val) {
+ if ("zero".equals(val))
+ return 0;
+ if ("one".equals(val))
+ return 1;
+ if ("user".equals(val))
+ return user;
+ return -1;
+ }
+ @SuppressWarnings("boxing")
+ public void set(String val, Object value) {
+ if ("user".equals(val)) {
+ if ("zero".equals(value))
+ user = 0;
+ else if ("one".equals(value))
+ user = 1;
+ else
+ user = value instanceof Integer? (Integer) value : -1;
+ }
+ }
+ }
+
+ @SuppressWarnings("boxing")
+ public void testDuck() throws Exception {
+ JexlEngine jexl = JEXL;
+ JexlContext jc = new MapContext();
+ jc.set("duck", new Duck());
+ Expression expr;
+ Object result;
+ expr = jexl.createExpression("duck.zero");
+ result = expr.evaluate(jc);
+ assertEquals(expr.toString(), 0, result);
+ expr = jexl.createExpression("duck.one");
+ result = expr.evaluate(jc);
+ assertEquals(expr.toString(), 1, result);
+ expr = jexl.createExpression("duck.user = 20");
+ result = expr.evaluate(jc);
+ assertEquals(expr.toString(), 20, result);
+ expr = jexl.createExpression("duck.user");
+ result = expr.evaluate(jc);
+ assertEquals(expr.toString(), 20, result);
+ expr = jexl.createExpression("duck.user = 'zero'");
+ result = expr.evaluate(jc);
+ expr = jexl.createExpression("duck.user");
+ result = expr.evaluate(jc);
+ assertEquals(expr.toString(), 0, result);
+ }
+
+ @SuppressWarnings("boxing")
+ public void testArray() throws Exception {
+ int[] array = { 100, 101 , 102 };
+ JexlEngine jexl = JEXL;
+ JexlContext jc = new MapContext();
+ jc.set("array", array);
+ Expression expr;
+ Object result;
+ expr = jexl.createExpression("array.1");
+ result = expr.evaluate(jc);
+ assertEquals(expr.toString(), 101, result);
+ expr = jexl.createExpression("array[1] = 1010");
+ result = expr.evaluate(jc);
+ assertEquals(expr.toString(), 1010, result);
+ expr = jexl.createExpression("array.0");
+ result = expr.evaluate(jc);
+ assertEquals(expr.toString(), 100, result);
+ }
+
+ /**
+ * Asserts that the given expression returns the given value when applied to the
+ * given context
+ */
+ protected void assertExpression(JexlContext jc, String expression, Object expected) throws Exception
+ {
+ Expression e = JEXL.createExpression(expression);
+ Object actual = e.evaluate(jc);
+ assertEquals(expression, expected, actual);
+ }
+
+}
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/JexlTestCase.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/JexlTestCase.java
new file mode 100644
index 0000000..96c88f1
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/JexlTestCase.java
@@ -0,0 +1,244 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.List;
+import java.util.ArrayList;
+
+import org.apache.commons.jexl2.parser.JexlNode;
+import org.apache.commons.jexl2.parser.ASTJexlScript;
+
+import junit.framework.TestCase;
+
+/**
+ * Implements runTest methods to dynamically instantiate and invoke a test,
+ * wrapping the call with setUp(), tearDown() calls.
+ * Eases the implementation of main methods to debug.
+ */
+public class JexlTestCase extends TestCase {
+ /** No parameters signature for test run. */
+ private static final Class<?>[] noParms = {};
+ /** String parameter signature for test run. */
+ private static final Class<?>[] stringParm = {String.class};
+
+ /** A default Jexl engine instance. */
+ protected final JexlEngine JEXL;
+
+ public JexlTestCase(String name) {
+ this(name, new JexlEngine());
+ }
+ protected JexlTestCase(String name, JexlEngine jexl) {
+ super(name);
+ JEXL = jexl;
+ JEXL.setCache(512);
+ }
+ public JexlTestCase() {
+ this(new JexlEngine());
+ }
+ protected JexlTestCase(JexlEngine jexl) {
+ super();
+ JEXL = jexl;
+ JEXL.setCache(512);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ debuggerCheck(JEXL);
+ }
+
+ /**
+ * Will force testing the debugger for each derived test class by
+ * recreating each expression from the JexlNode in the JexlEngine cache &
+ * testing them for equality with the origin.
+ * @throws Exception
+ */
+ public static void debuggerCheck(JexlEngine jexl) throws Exception {
+ // without a cache, nothing to check
+ if (jexl.cache == null) {
+ return;
+ }
+ JexlEngine jdbg = new JexlEngine();
+ Debugger dbg = new Debugger();
+ // iterate over all expression in cache
+ Iterator<Map.Entry<String,ASTJexlScript>> inodes = jexl.cache.entrySet().iterator();
+ while (inodes.hasNext()) {
+ Map.Entry<String,ASTJexlScript> entry = inodes.next();
+ JexlNode node = entry.getValue();
+ // recreate expr string from AST
+ dbg.debug(node);
+ String expressiondbg = dbg.data();
+ // recreate expr from string
+ Expression exprdbg = jdbg.createExpression(expressiondbg);
+ // make arg cause become the root cause
+ JexlNode root = ((ExpressionImpl) exprdbg).script;
+ while (root.jjtGetParent() != null) {
+ root = root.jjtGetParent();
+ }
+ // test equality
+ String reason = JexlTestCase.checkEquals(root, node);
+ if (reason != null) {
+ throw new RuntimeException("debugger equal failed: "
+ + expressiondbg
+ +" /**** " +reason+" **** */ "
+ + entry.getKey());
+ }
+ }
+ }
+
+ /**
+ * Creates a list of all descendants of a script including itself.
+ * @param script the script to flatten
+ * @return the descendants-and-self list
+ */
+ private static ArrayList<JexlNode> flatten(JexlNode node) {
+ ArrayList<JexlNode> list = new ArrayList<JexlNode>();
+ flatten(list, node);
+ return list;
+ }
+
+ /**
+ * Recursively adds all children of a script to the list of descendants.
+ * @param list the list of descendants to add to
+ * @param script the script & descendants to add
+ */
+ private static void flatten(List<JexlNode> list, JexlNode node) {
+ int nc = node.jjtGetNumChildren();
+ list.add(node);
+ for(int c = 0; c < nc; ++c) {
+ flatten(list, node.jjtGetChild(c));
+ }
+ }
+
+ /**
+ * Checks the equality of 2 nodes by comparing all their descendants.
+ * Descendants must have the same class and same image if non null.
+ * @param lhs the left script
+ * @param rhs the right script
+ * @return null if true, a reason otherwise
+ */
+ private static String checkEquals(JexlNode lhs, JexlNode rhs) {
+ if (lhs != rhs) {
+ ArrayList<JexlNode> lhsl = flatten(lhs);
+ ArrayList<JexlNode> rhsl = flatten(rhs);
+ if (lhsl.size() != rhsl.size()) {
+ return "size: " + lhsl.size() + " != " + rhsl.size();
+ }
+ for(int n = 0; n < lhsl.size(); ++n) {
+ lhs = lhsl.get(n);
+ rhs = rhsl.get(n);
+ if (lhs.getClass() != rhs.getClass()) {
+ return "class: " + lhs.getClass() + " != " + rhs.getClass();
+ }
+ if ((lhs.image == null && rhs.image != null)
+ || (lhs.image != null && rhs.image == null)) {
+ return "image: " + lhs.image + " != " + rhs.image;
+ }
+ if (lhs.image != null && !lhs.image.equals(rhs.image)) {
+ return "image: " + lhs.image + " != " + rhs.image;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Dynamically runs a test method.
+ * @param name the test method to run
+ * @throws Exception if anything goes wrong
+ */
+ public void runTest(String name) throws Exception {
+ if ("runTest".equals(name)) {
+ return;
+ }
+ Method method = null;
+ try {
+ method = this.getClass().getDeclaredMethod(name, noParms);
+ }
+ catch(Exception xany) {
+ fail("no such test: " + name);
+ return;
+ }
+ try {
+ this.setUp();
+ method.invoke(this);
+ } finally {
+ this.tearDown();
+ }
+ }
+
+ /**
+ * Instantiate and runs a test method; useful for debugging purpose.
+ * For instance:
+ * <code>
+ * public static void main(String[] args) throws Exception {
+ * runTest("BitwiseOperatorTest","testAndVariableNumberCoercion");
+ * }
+ * </code>
+ * @param tname the test class name
+ * @param mname the test class method
+ * @throws Exception
+ */
+ public static void runTest(String tname, String mname) throws Exception {
+ String testClassName = "org.apache.commons.jexl2."+tname;
+ Class<JexlTestCase> clazz = null;
+ JexlTestCase test = null;
+ // find the class
+ try {
+ clazz = (Class<JexlTestCase>) Class.forName(testClassName);
+ }
+ catch(ClassNotFoundException xclass) {
+ fail("no such class: " + testClassName);
+ return;
+ }
+ // find ctor & instantiate
+ Constructor<JexlTestCase> ctor = null;
+ try {
+ ctor = clazz.getConstructor(stringParm);
+ test = ctor.newInstance("debug");
+ }
+ catch(NoSuchMethodException xctor) {
+ // instantiate default class ctor
+ try {
+ test = clazz.newInstance();
+ }
+ catch(Exception xany) {
+ fail("cant instantiate test: " + xany);
+ return;
+ }
+ }
+ catch(Exception xany) {
+ fail("cant instantiate test: " + xany);
+ return;
+ }
+ // Run the test
+ test.runTest(mname);
+ }
+
+ /**
+ * Runs a test.
+ * @param args where args[0] is the test class name and args[1] the test class method
+ * @throws Exception
+ */
+ public static void main(String[] args) throws Exception {
+ runTest(args[0], args[1]);
+ }
+}
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/MapLiteralTest.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/MapLiteralTest.java
new file mode 100644
index 0000000..35a41d3
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/MapLiteralTest.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Tests for map literals
+ *
+ * @author Peter Royal
+ * @since 1.2
+ */
+public class MapLiteralTest extends JexlTestCase {
+
+ public void testLiteralWithStrings() throws Exception {
+ Expression e = JEXL.createExpression( "{ 'foo' : 'bar' }" );
+ JexlContext jc = new MapContext();
+
+ Object o = e.evaluate( jc );
+ assertEquals( Collections.singletonMap( "foo", "bar" ), o );
+ }
+
+ public void testLiteralWithMultipleEntries() throws Exception {
+ Expression e = JEXL.createExpression( "{ 'foo' : 'bar', 'eat' : 'food' }" );
+ JexlContext jc = new MapContext();
+
+ Map<String, String> expected = new HashMap<String, String>();
+ expected.put( "foo", "bar" );
+ expected.put( "eat", "food" );
+
+ Object o = e.evaluate( jc );
+ assertEquals( expected, o );
+ }
+
+ public void testLiteralWithNumbers() throws Exception {
+ Expression e = JEXL.createExpression( "{ 5 : 10 }" );
+ JexlContext jc = new MapContext();
+
+ Object o = e.evaluate( jc );
+ assertEquals( Collections.singletonMap( new Integer( 5 ), new Integer( 10 ) ), o );
+
+ e = JEXL.createExpression("m = { 3 : 30, 4 : 40, 5 : 'fifty', '7' : 'seven', 7 : 'SEVEN' }");
+ e.evaluate(jc);
+
+ e = JEXL.createExpression("m.3");
+ o = e.evaluate(jc);
+ assertEquals(new Integer(30), o);
+
+ e = JEXL.createExpression("m[4]");
+ o = e.evaluate(jc);
+ assertEquals(new Integer(40), o);
+
+ jc.set("i", Integer.valueOf(5));
+ e = JEXL.createExpression("m[i]");
+ o = e.evaluate(jc);
+ assertEquals("fifty", o);
+
+ e = JEXL.createExpression("m.3 = 'thirty'");
+ e.evaluate(jc);
+ e = JEXL.createExpression("m.3");
+ o = e.evaluate(jc);
+ assertEquals("thirty", o);
+
+ e = JEXL.createExpression("m['7']");
+ o = e.evaluate(jc);
+ assertEquals("seven", o);
+
+ e = JEXL.createExpression("m.7");
+ o = e.evaluate(jc);
+ assertEquals("SEVEN", o);
+
+ jc.set("k", Integer.valueOf(7));
+ e = JEXL.createExpression("m[k]");
+ o = e.evaluate(jc);
+ assertEquals("SEVEN", o);
+
+ jc.set("k", "7");
+ e = JEXL.createExpression("m[k]");
+ o = e.evaluate(jc);
+ assertEquals("seven", o);
+ }
+
+ public void testSizeOfSimpleMapLiteral() throws Exception {
+ Expression e = JEXL.createExpression( "size({ 'foo' : 'bar' })" );
+ JexlContext jc = new MapContext();
+
+ Object o = e.evaluate( jc );
+ assertEquals( new Integer( 1 ), o );
+ }
+
+ public void testCallingMethodsOnNewMapLiteral() throws Exception {
+ Expression e = JEXL.createExpression( "size({ 'foo' : 'bar' }.values())" );
+ JexlContext jc = new MapContext();
+
+ Object o = e.evaluate( jc );
+ assertEquals( new Integer( 1 ), o );
+ }
+
+ public void testNotEmptySimpleMapLiteral() throws Exception {
+ Expression e = JEXL.createExpression( "empty({ 'foo' : 'bar' })" );
+ JexlContext jc = new MapContext();
+
+ Object o = e.evaluate( jc );
+ assertFalse( ( (Boolean) o ).booleanValue() );
+ }
+
+ public void testMapMapLiteral() throws Exception {
+ Expression e = JEXL.createExpression( "{'foo' : { 'inner' : 'bar' }}" );
+ JexlContext jc = new MapContext();
+ Object o = e.evaluate( jc );
+ assertNotNull(o);
+
+ jc.set("outer", o);
+ e = JEXL.createExpression("outer.foo.inner");
+ o = e.evaluate( jc );
+ assertEquals( "bar", o );
+ }
+
+ public void testMapArrayLiteral() throws Exception {
+ Expression e = JEXL.createExpression( "{'foo' : [ 'inner' , 'bar' ]}" );
+ JexlContext jc = new MapContext();
+ Object o = e.evaluate( jc );
+ assertNotNull(o);
+
+ jc.set("outer", o);
+ e = JEXL.createExpression("outer.foo.1");
+ o = e.evaluate( jc );
+ assertEquals( "bar", o );
+ }
+
+}
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/MethodTest.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/MethodTest.java
new file mode 100644
index 0000000..63af1b3
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/MethodTest.java
@@ -0,0 +1,207 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+
+import org.apache.commons.jexl2.junit.Asserter;
+
+/**
+ * Tests for calling methods on objects
+ *
+ * @since 2.0
+ */
+public class MethodTest extends JexlTestCase {
+
+ private Asserter asserter;
+
+ private static final String METHOD_STRING = "Method string";
+
+ public static class TestClass {
+ public String testVarArgs(Integer[] args) {
+ return "Test";
+ }
+ }
+
+ public static class Functor {
+ public int ten() {
+ return 10;
+ }
+ public int plus10(int num) {
+ return num + 10;
+ }
+ public static int TWENTY() {
+ return 20;
+ }
+ public static int PLUS20(int num) {
+ return num + 20;
+ }
+ public static Class<?> NPEIfNull(Object x) {
+ return x.getClass();
+ }
+ }
+
+ public static class EnhancedContext extends MapContext {
+ int factor = 6;
+ }
+
+ public static class ContextualFunctor {
+ private final EnhancedContext context;
+ public ContextualFunctor(EnhancedContext theContext) {
+ context = theContext;
+ }
+ public int ratio(int n) {
+ context.factor -= 1;
+ return n / context.factor;
+ }
+ }
+
+ @Override
+ public void setUp() {
+ asserter = new Asserter(JEXL);
+ }
+
+ public void testCallVarArgMethod() throws Exception {
+ asserter.setVariable("test", new TestClass());
+ asserter.assertExpression("test.testVarArgs(1,2,3,4,5)", "Test");
+ }
+
+ public void testInvoke() throws Exception {
+ Functor func = new Functor();
+ assertEquals(Integer.valueOf(10), JEXL.invokeMethod(func, "ten"));
+ assertEquals(Integer.valueOf(42), JEXL.invokeMethod(func, "PLUS20", Integer.valueOf(22)));
+ try {
+ JEXL.invokeMethod(func, "nonExistentMethod");
+ fail("method does not exist!");
+ } catch(Exception xj0) {
+ // ignore
+ }
+ try {
+ JEXL.invokeMethod(func, "NPEIfNull", (Object[]) null);
+ fail("method should have thrown!");
+ } catch(Exception xj0) {
+ // ignore
+ }
+ }
+
+ /**
+ * test a simple method expression
+ */
+ public void testMethod() throws Exception {
+ // tests a simple method expression
+ asserter.setVariable("foo", new Foo());
+ asserter.assertExpression("foo.bar()", METHOD_STRING);
+ }
+
+ public void testMulti() throws Exception {
+ asserter.setVariable("foo", new Foo());
+ asserter.assertExpression("foo.innerFoo.bar()", METHOD_STRING);
+ }
+
+ /**
+ * test some String method calls
+ */
+ public void testStringMethods() throws Exception {
+ asserter.setVariable("foo", "abcdef");
+ asserter.assertExpression("foo.substring(3)", "def");
+ asserter.assertExpression("foo.substring(0,(size(foo)-3))", "abc");
+ asserter.assertExpression("foo.substring(0,size(foo)-3)", "abc");
+ asserter.assertExpression("foo.substring(0,foo.length()-3)", "abc");
+ asserter.assertExpression("foo.substring(0, 1+1)", "ab");
+ }
+
+ /**
+ * Ensures static methods on objects can be called.
+ */
+ public void testStaticMethodInvocation() throws Exception {
+ asserter.setVariable("aBool", Boolean.FALSE);
+ asserter.assertExpression("aBool.valueOf('true')", Boolean.TRUE);
+ }
+
+ public void testStaticMethodInvocationOnClasses() throws Exception {
+ asserter.setVariable("Boolean", Boolean.class);
+ asserter.assertExpression("Boolean.valueOf('true')", Boolean.TRUE);
+ }
+
+ public static class MyMath {
+ public double cos(double x) {
+ return Math.cos(x);
+ }
+ }
+
+ public void testTopLevelCall() throws Exception {
+ java.util.Map<String, Object> funcs = new java.util.HashMap<String, Object>();
+ funcs.put(null, new Functor());
+ funcs.put("math", new MyMath());
+ funcs.put("cx", ContextualFunctor.class);
+ JEXL.setFunctions(funcs);
+
+ JexlContext jc = new EnhancedContext();
+
+ Expression e = JEXL.createExpression("ten()");
+ Object o = e.evaluate(jc);
+ assertEquals("Result is not 10", new Integer(10), o);
+
+ e = JEXL.createExpression("plus10(10)");
+ o = e.evaluate(jc);
+ assertEquals("Result is not 20", new Integer(20), o);
+
+ e = JEXL.createExpression("plus10(ten())");
+ o = e.evaluate(jc);
+ assertEquals("Result is not 20", new Integer(20), o);
+
+ jc.set("pi", new Double(Math.PI));
+ e = JEXL.createExpression("math:cos(pi)");
+ o = e.evaluate(jc);
+ assertEquals(Double.valueOf(-1),o);
+
+ e = JEXL.createExpression("cx:ratio(10) + cx:ratio(20)");
+ o = e.evaluate(jc);
+ assertEquals(Integer.valueOf(7),o);
+ }
+
+ public void testNamespaceCall() throws Exception {
+ java.util.Map<String, Object> funcs = new java.util.HashMap<String, Object>();
+ funcs.put("func", new Functor());
+ funcs.put("FUNC", Functor.class);
+ JEXL.setFunctions(funcs);
+
+ Expression e = JEXL.createExpression("func:ten()");
+ JexlContext jc = new MapContext();
+ Object o = e.evaluate(jc);
+ assertEquals("Result is not 10", new Integer(10), o);
+
+ e = JEXL.createExpression("func:plus10(10)");
+ jc = new MapContext();
+ o = e.evaluate(jc);
+ assertEquals("Result is not 20", new Integer(20), o);
+
+ e = JEXL.createExpression("func:plus10(func:ten())");
+ jc = new MapContext();
+ o = e.evaluate(jc);
+ assertEquals("Result is not 20", new Integer(20), o);
+
+ e = JEXL.createExpression("FUNC:PLUS20(10)");
+ jc = new MapContext();
+ o = e.evaluate(jc);
+ assertEquals("Result is not 30", new Integer(30), o);
+
+ e = JEXL.createExpression("FUNC:PLUS20(FUNC:TWENTY())");
+ jc = new MapContext();
+ o = e.evaluate(jc);
+ assertEquals("Result is not 40", new Integer(40), o);
+ }
+
+}
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/ParseFailuresTest.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/ParseFailuresTest.java
new file mode 100644
index 0000000..0a3c41b
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/ParseFailuresTest.java
@@ -0,0 +1,100 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+
+/**
+ * Tests for malformed expressions and scripts.
+ * ({@link org.apache.commons.jexl2.JexlEngine#createExpression(String)} and
+ * {@link org.apache.commons.jexl2.JexlEngine#createScript(String)} should throw
+ * {@link org.apache.commons.jexl2.JexlException}s).
+ *
+ * @since 1.1
+ */
+public class ParseFailuresTest extends JexlTestCase {
+
+ /**
+ * Create the test.
+ *
+ * @param testName name of the test
+ */
+ public ParseFailuresTest(String testName) {
+ super(testName);
+ JEXL.setSilent(false);
+ }
+
+ public void testMalformedExpression1() throws Exception {
+ // this will throw a JexlException
+ String badExpression = "eq";
+ try {
+ JEXL.createExpression(badExpression);
+ fail("Parsing \"" + badExpression
+ + "\" should result in a JexlException");
+ } catch (JexlException pe) {
+ // expected
+ }
+ }
+
+ public void testMalformedExpression2() throws Exception {
+ // this will throw a TokenMgrErr, which we rethrow as a JexlException
+ String badExpression = "?";
+ try {
+ JEXL.createExpression(badExpression);
+ fail("Parsing \"" + badExpression
+ + "\" should result in a JexlException");
+ } catch (JexlException pe) {
+ // expected
+ }
+ }
+
+ public void testMalformedScript1() throws Exception {
+ // this will throw a TokenMgrErr, which we rethrow as a JexlException
+ String badScript = "eq";
+ try {
+ JEXL.createScript(badScript);
+ fail("Parsing \"" + badScript
+ + "\" should result in a JexlException");
+ } catch (JexlException pe) {
+ // expected
+ }
+ }
+
+
+ public void testMalformedScript2() throws Exception {
+ // this will throw a TokenMgrErr, which we rethrow as a JexlException
+ String badScript = "?";
+ try {
+ JEXL.createScript(badScript);
+ fail("Parsing \"" + badScript
+ + "\" should result in a JexlException");
+ } catch (JexlException pe) {
+ // expected
+ }
+ }
+
+ public void testMalformedScript3() throws Exception {
+ // this will throw a TokenMgrErr, which we rethrow as a JexlException
+ String badScript = "foo=1;bar=2;a?b:c:d;";
+ try {
+ JEXL.createScript(badScript);
+ fail("Parsing \"" + badScript
+ + "\" should result in a JexlException");
+ } catch (JexlException pe) {
+ // expected
+ }
+ }
+
+}
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/PublicFieldsTest.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/PublicFieldsTest.java
new file mode 100644
index 0000000..6b6ed26
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/PublicFieldsTest.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+
+import org.apache.commons.jexl2.introspection.UberspectImpl;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Tests public field set/get.
+ */
+public class PublicFieldsTest extends JexlTestCase {
+ // some constants
+ private static final String LOWER42 = "fourty-two";
+ private static final String UPPER42 = "FOURTY-TWO";
+ /**
+ * An Inner class.
+ */
+ public static class Inner {
+ public double aDouble = 42.0;
+ }
+
+ /**
+ * A Struct, all fields public
+ */
+ public static class Struct {
+ public Inner inner = new Inner();
+ public int anInt = 42;
+ public String aString = LOWER42;
+ }
+
+ // a pub instance
+ private Struct pub;
+ // the JexlContext to use
+ private JexlContext ctxt;
+
+ public PublicFieldsTest() {
+ JEXL.setLenient(false);
+ }
+
+ @Override
+ public void setUp() {
+ pub = new Struct();
+ ctxt = new MapContext();
+ ctxt.set("pub", pub);
+ }
+
+ public void testGetInt() throws Exception {
+ Expression get = JEXL.createExpression("pub.anInt");
+ assertEquals(42, get.evaluate(ctxt));
+ JEXL.setProperty(pub, "anInt", -42);
+ assertEquals(-42, get.evaluate(ctxt));
+ }
+
+ public void testSetInt() throws Exception {
+ Expression set = JEXL.createExpression("pub.anInt = value");
+ ctxt.set("value", -42);
+ assertEquals(-42, set.evaluate(ctxt));
+ assertEquals(-42, JEXL.getProperty(pub, "anInt"));
+ ctxt.set("value", 42);
+ assertEquals(42, set.evaluate(ctxt));
+ assertEquals(42, JEXL.getProperty(pub, "anInt"));
+ try {
+ ctxt.set("value", UPPER42);
+ assertEquals(null, set.evaluate(ctxt));
+ fail("should have thrown");
+ } catch(JexlException xjexl) {}
+ }
+
+ public void testGetString() throws Exception {
+ Expression get = JEXL.createExpression("pub.aString");
+ assertEquals(LOWER42, get.evaluate(ctxt));
+ JEXL.setProperty(pub, "aString", UPPER42);
+ assertEquals(UPPER42, get.evaluate(ctxt));
+ }
+
+ public void testSetString() throws Exception {
+ Expression set = JEXL.createExpression("pub.aString = value");
+ ctxt.set("value", UPPER42);
+ assertEquals(UPPER42, set.evaluate(ctxt));
+ assertEquals(UPPER42, JEXL.getProperty(pub, "aString"));
+ ctxt.set("value", LOWER42);
+ assertEquals(LOWER42, set.evaluate(ctxt));
+ assertEquals(LOWER42, JEXL.getProperty(pub, "aString"));
+ }
+
+ public void testGetInnerDouble() throws Exception {
+ Expression get = JEXL.createExpression("pub.inner.aDouble");
+ assertEquals(42.0, get.evaluate(ctxt));
+ JEXL.setProperty(pub, "inner.aDouble", -42);
+ assertEquals(-42.0, get.evaluate(ctxt));
+ }
+
+ public void testSetInnerDouble() throws Exception {
+ Expression set = JEXL.createExpression("pub.inner.aDouble = value");
+ ctxt.set("value", -42.0);
+ assertEquals(-42.0, set.evaluate(ctxt));
+ assertEquals(-42.0, JEXL.getProperty(pub, "inner.aDouble"));
+ ctxt.set("value", 42.0);
+ assertEquals(42.0, set.evaluate(ctxt));
+ assertEquals(42.0, JEXL.getProperty(pub, "inner.aDouble"));
+ try {
+ ctxt.set("value", UPPER42);
+ assertEquals(null, set.evaluate(ctxt));
+ fail("should have thrown");
+ } catch(JexlException xjexl) {}
+ }
+
+}
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/ScriptTest.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/ScriptTest.java
new file mode 100644
index 0000000..aa8ba18
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/ScriptTest.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+
+import java.io.File;
+import java.net.URL;
+
+/**
+ * Tests for Script
+ * @since 1.1
+ */
+public class ScriptTest extends JexlTestCase {
+ static final String TEST1 = "src/test/scripts/test1.jexl";
+
+ // test class for testScriptUpdatesContext
+ // making this class private static will cause the test to fail.
+ // this is due to unusual code in ClassMap.getAccessibleMethods(Class)
+ // that treats non-public classes in a specific way. Why getAccessibleMethods
+ // does this is not known yet.
+ public static class Tester {
+ private String code;
+ public String getCode () {
+ return code;
+ }
+ public void setCode(String c) {
+ code = c;
+ }
+ }
+ /**
+ * Create a new test case.
+ * @param name case name
+ */
+ public ScriptTest(String name) {
+ super(name);
+ }
+
+ /**
+ * Test creating a script from a string.
+ */
+ public void testSimpleScript() throws Exception {
+ String code = "while (x < 10) x = x + 1;";
+ Script s = JEXL.createScript(code);
+ JexlContext jc = new MapContext();
+ jc.set("x", new Integer(1));
+
+ Object o = s.execute(jc);
+ assertEquals("Result is wrong", new Integer(10), o);
+ assertEquals("getText is wrong", code, s.getText());
+ }
+
+ public void testScriptFromFile() throws Exception {
+ File testScript = new File(TEST1);
+ Script s = JEXL.createScript(testScript);
+ JexlContext jc = new MapContext();
+ jc.set("out", System.out);
+ Object result = s.execute(jc);
+ assertNotNull("No result", result);
+ assertEquals("Wrong result", new Integer(7), result);
+ }
+
+ public void testScriptFromURL() throws Exception {
+ URL testUrl = new File("src/test/scripts/test1.jexl").toURI().toURL();
+ Script s = JEXL.createScript(testUrl);
+ JexlContext jc = new MapContext();
+ jc.set("out", System.out);
+ Object result = s.execute(jc);
+ assertNotNull("No result", result);
+ assertEquals("Wrong result", new Integer(7), result);
+ }
+
+ public void testScriptUpdatesContext() throws Exception {
+ String jexlCode = "resultat.setCode('OK')";
+ Expression e = JEXL.createExpression(jexlCode);
+ Script s = JEXL.createScript(jexlCode);
+
+ Tester resultatJexl = new Tester();
+ JexlContext jc = new MapContext();
+ jc.set("resultat", resultatJexl);
+
+ resultatJexl.setCode("");
+ e.evaluate(jc);
+ assertEquals("OK", resultatJexl.getCode());
+ resultatJexl.setCode("");
+ s.execute(jc);
+ assertEquals("OK", resultatJexl.getCode());
+ }
+
+}
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/UnifiedJEXLTest.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/UnifiedJEXLTest.java
new file mode 100644
index 0000000..42ddeb3
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/UnifiedJEXLTest.java
@@ -0,0 +1,241 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+/**
+ * Test cases for the UnifiedEL.
+ */
+public class UnifiedJEXLTest extends JexlTestCase {
+ private static final JexlEngine ENGINE = new JexlEngine();
+ static {
+ ENGINE.setLenient(false);
+ ENGINE.setSilent(false);
+ ENGINE.setCache(128);
+ }
+ private static final UnifiedJEXL EL = new UnifiedJEXL(ENGINE);
+ private static final Log LOG = LogFactory.getLog(UnifiedJEXL.class);
+ private JexlContext context = null;
+ private Map<String,Object> vars =null;
+
+ @Override
+ public void setUp() throws Exception {
+ // ensure jul logging is only error
+ java.util.logging.Logger.getLogger(JexlEngine.class.getName()).setLevel(java.util.logging.Level.SEVERE);
+ vars = new HashMap<String,Object>();
+ context = new MapContext(vars);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ debuggerCheck(ENGINE);
+ super.tearDown();
+ }
+
+ public static class Froboz {
+ int value;
+ public Froboz(int v) {
+ value = v;
+ }
+ public void setValue(int v) {
+ value = v;
+ }
+ public int getValue() {
+ return value;
+ }
+ public int plus10() {
+ int i = value;
+ value += 10;
+ return i;
+ }
+ }
+
+ public UnifiedJEXLTest(String testName) {
+ super(testName);
+ }
+
+ public void testStatement() throws Exception {
+ vars.put("froboz", new Froboz(123));
+ UnifiedJEXL.Expression check = EL.parse("${froboz.value = 32; froboz.plus10(); froboz.value}");
+ Object o = check.evaluate(context);
+ assertEquals("Result is not 42", new Integer(42), o);
+ }
+
+ public void testAssign() throws Exception {
+ UnifiedJEXL.Expression assign = EL.parse("${froboz.value = 10}");
+ UnifiedJEXL.Expression check = EL.parse("${froboz.value}");
+ Object o = assign.evaluate(context);
+ assertEquals("Result is not 10", new Integer(10), o);
+ o = check.evaluate(context);
+ assertEquals("Result is not 10", new Integer(10), o);
+ }
+
+ public void testComposite() throws Exception {
+ UnifiedJEXL.Expression expr = EL.parse("Dear ${p} ${name};");
+ vars.put("p", "Mr");
+ vars.put("name", "Doe");
+ assertTrue("expression should be immediate", expr.isImmediate());
+ Object o = expr.evaluate(context);
+ assertEquals("Dear Mr Doe;", o);
+ vars.put("p", "Ms");
+ vars.put("name", "Jones");
+ o = expr.evaluate(context);
+ assertEquals("Dear Ms Jones;", o);
+ }
+
+ public void testPrepareEvaluate() throws Exception {
+ UnifiedJEXL.Expression expr = EL.parse("Dear #{p} ${name};");
+ assertTrue("expression should be deferred", expr.isDeferred());
+ vars.put("name", "Doe");
+ UnifiedJEXL.Expression phase1 = expr.prepare(context);
+ String as = phase1.asString();
+ assertEquals("Dear #{p} Doe;", as);
+ vars.put("p", "Mr");
+ vars.put("name", "Should not be used in 2nd phase");
+ Object o = phase1.evaluate(context);
+ assertEquals("Dear Mr Doe;", o);
+ }
+
+ public void testNested() throws Exception {
+ UnifiedJEXL.Expression expr = EL.parse("#{${hi}+'.world'}");
+ vars.put("hi", "hello");
+ vars.put("hello.world", "Hello World!");
+ Object o = expr.evaluate(context);
+ assertTrue("source should not be expression", expr.getSource() != expr.prepare(context));
+ assertTrue("expression should be deferred", expr.isDeferred());
+ assertEquals("Hello World!", o);
+ }
+
+ public void testImmediate() throws Exception {
+ JexlContext none = null;
+ UnifiedJEXL.Expression expr = EL.parse("${'Hello ' + 'World!'}");
+ assertTrue("prepare should return same expression", expr.prepare(none) == expr);
+ Object o = expr.evaluate(none);
+ assertTrue("expression should be immediate", expr.isImmediate());
+ assertEquals("Hello World!", o);
+ }
+
+ public void testConstant() throws Exception {
+ JexlContext none = null;
+ UnifiedJEXL.Expression expr = EL.parse("Hello World!");
+ assertTrue("prepare should return same expression", expr.prepare(none) == expr);
+ Object o = expr.evaluate(none);
+ assertTrue("expression should be immediate", expr.isImmediate());
+ assertEquals("Hello World!", o);
+ }
+
+ public void testDeferred() throws Exception {
+ JexlContext none = null;
+ UnifiedJEXL.Expression expr = EL.parse("#{'world'}");
+ assertTrue("prepare should return same expression", expr.prepare(none) == expr);
+ Object o = expr.evaluate(none);
+ assertTrue("expression should be deferred", expr.isDeferred());
+ assertEquals("world", o);
+ }
+
+ public void testEscape() throws Exception {
+ JexlContext none = null;
+ UnifiedJEXL.Expression expr;
+ Object o;
+ // $ and # are escapable in UnifiedJEXL
+ expr = EL.parse("\\#{'world'}");
+ o = expr.evaluate(none);
+ assertEquals("#{'world'}", o);
+ expr = EL.parse("\\${'world'}");
+ o = expr.evaluate(none);
+ assertEquals("${'world'}", o);
+ }
+
+ public void testEscapeString() throws Exception {
+ UnifiedJEXL.Expression expr = EL.parse("\\\"${'world\\'s finest'}\\\"");
+ JexlContext none = null;
+ Object o = expr.evaluate(none);
+ assertEquals("\"world's finest\"", o);
+ }
+
+ public void testNonEscapeString() throws Exception {
+ UnifiedJEXL.Expression expr = EL.parse("c:\\some\\windows\\path");
+ JexlContext none = null;
+ Object o = expr.evaluate(none);
+ assertEquals("c:\\some\\windows\\path", o);
+ }
+
+ public void testMalformed() throws Exception {
+ try {
+ UnifiedJEXL.Expression expr = EL.parse("${'world'");
+ JexlContext none = null;
+ expr.evaluate(none);
+ fail("should be malformed");
+ }
+ catch(UnifiedJEXL.Exception xjexl) {
+ // expected
+ String xmsg = xjexl.getMessage();
+ LOG.warn(xmsg);
+ }
+ }
+
+ public void testMalformedNested() throws Exception {
+ try {
+ UnifiedJEXL.Expression expr = EL.parse("#{${hi} world}");
+ JexlContext none = null;
+ expr.evaluate(none);
+ fail("should be malformed");
+ }
+ catch(UnifiedJEXL.Exception xjexl) {
+ // expected
+ String xmsg = xjexl.getMessage();
+ LOG.warn(xmsg);
+ }
+ }
+
+ public void testBadContextNested() throws Exception {
+ try {
+ UnifiedJEXL.Expression expr = EL.parse("#{${hi}+'.world'}");
+ JexlContext none = null;
+ expr.evaluate(none);
+ fail("should be malformed");
+ }
+ catch(UnifiedJEXL.Exception xjexl) {
+ // expected
+ String xmsg = xjexl.getMessage();
+ LOG.warn(xmsg);
+ }
+ }
+
+ public void testCharAtBug() throws Exception {
+ vars.put("foo", "abcdef");
+ UnifiedJEXL.Expression expr = EL.parse("${foo.substring(2,4)/*comment*/}");
+ Object o = expr.evaluate(context);
+ assertEquals("cd", o);
+
+ vars.put("bar", "foo");
+ try {
+ ENGINE.setSilent(true);
+ expr = EL.parse("#{${bar}+'.charAt(-2)'}");
+ expr = expr.prepare(context);
+ o = expr.evaluate(context);
+ assertEquals(null, o);
+ }
+ finally {
+ ENGINE.setSilent(false);
+ }
+
+ }
+
+}
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/WhileTest.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/WhileTest.java
new file mode 100644
index 0000000..2540f4d
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/WhileTest.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2;
+
+/**
+ * Tests for while statement.
+ * @author Dion Gillard
+ * @since 1.1
+ */
+public class WhileTest extends JexlTestCase {
+
+ public WhileTest(String testName) {
+ super(testName);
+ }
+
+ public void testSimpleWhileFalse() throws Exception {
+ Expression e = JEXL.createExpression("while (false) ;");
+ JexlContext jc = new MapContext();
+
+ Object o = e.evaluate(jc);
+ assertNull("Result is not null", o);
+ }
+
+ public void testWhileExecutesExpressionWhenLooping() throws Exception {
+ Expression e = JEXL.createExpression("while (x < 10) x = x + 1;");
+ JexlContext jc = new MapContext();
+ jc.set("x", new Integer(1));
+
+ Object o = e.evaluate(jc);
+ assertEquals("Result is wrong", new Integer(10), o);
+ }
+
+ public void testWhileWithBlock() throws Exception {
+ Expression e = JEXL.createExpression("while (x < 10) { x = x + 1; y = y * 2; }");
+ JexlContext jc = new MapContext();
+ jc.set("x", new Integer(1));
+ jc.set("y", new Integer(1));
+
+ Object o = e.evaluate(jc);
+ assertEquals("Result is wrong", new Integer(512), o);
+ assertEquals("x is wrong", new Integer(10), jc.get("x"));
+ assertEquals("y is wrong", new Integer(512), jc.get("y"));
+ }
+}
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/examples/ArrayTest.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/examples/ArrayTest.java
new file mode 100644
index 0000000..03085a0
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/examples/ArrayTest.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2.examples;
+
+import org.apache.commons.jexl2.*;
+import junit.framework.TestCase;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Simple example to show how to access arrays.
+ *
+ * @since 1.0
+ * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class ArrayTest extends TestCase {
+ /**
+ * An example for array access.
+ */
+ static void example(Output out) throws Exception {
+ /**
+ * First step is to retrieve an instance of a JexlEngine;
+ * it might be already existing and shared or created anew.
+ */
+ JexlEngine jexl = new JexlEngine();
+ /*
+ * Second make a jexlContext and put stuff in it
+ */
+ JexlContext jc = new MapContext();
+
+ List<Object> l = new ArrayList<Object>();
+ l.add("Hello from location 0");
+ Integer two = new Integer(2);
+ l.add(two);
+ jc.set("array", l);
+
+ Expression e = jexl.createExpression("array[1]");
+ Object o = e.evaluate(jc);
+ out.print("Object @ location 1 = ", o, two);
+
+ e = jexl.createExpression("array[0].length()");
+ o = e.evaluate(jc);
+
+ out.print("The length of the string at location 0 is : ", o, Integer.valueOf(21));
+ }
+
+ /**
+ * Unit test entry point.
+ * @throws Exception
+ */
+ public void testExample() throws Exception {
+ example(Output.JUNIT);
+ }
+
+ /**
+ * Command line entry point.
+ * @param args command line arguments
+ * @throws Exception cos jexl does.
+ */
+ public static void main(String[] args) throws Exception {
+ example(Output.SYSTEM);
+ }
+}
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/examples/MethodPropertyTest.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/examples/MethodPropertyTest.java
new file mode 100644
index 0000000..bd21fdd
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/examples/MethodPropertyTest.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2.examples;
+
+import org.apache.commons.jexl2.*;
+import junit.framework.TestCase;
+
+/**
+ * Simple example to show how to access method and properties.
+ *
+ * @since 1.0
+ * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class MethodPropertyTest extends TestCase {
+ /**
+ * An example for method access.
+ */
+ public static void example(final Output out) throws Exception {
+ /**
+ * First step is to retrieve an instance of a JexlEngine;
+ * it might be already existing and shared or created anew.
+ */
+ JexlEngine jexl = new JexlEngine();
+ /*
+ * Second make a jexlContext and put stuff in it
+ */
+ JexlContext jc = new MapContext();
+
+ /**
+ * The Java equivalents of foo and number for comparison and checking
+ */
+ Foo foo = new Foo();
+ Integer number = new Integer(10);
+
+ jc.set("foo", foo);
+ jc.set("number", number);
+
+ /*
+ * access a method w/o args
+ */
+ Expression e = jexl.createExpression("foo.getFoo()");
+ Object o = e.evaluate(jc);
+ out.print("value returned by the method getFoo() is : ", o, foo.getFoo());
+
+ /*
+ * access a method w/ args
+ */
+ e = jexl.createExpression("foo.convert(1)");
+ o = e.evaluate(jc);
+ out.print("value of " + e.getExpression() + " is : ", o, foo.convert(1));
+
+ e = jexl.createExpression("foo.convert(1+7)");
+ o = e.evaluate(jc);
+ out.print("value of " + e.getExpression() + " is : ", o, foo.convert(1+7));
+
+ e = jexl.createExpression("foo.convert(1+number)");
+ o = e.evaluate(jc);
+ out.print("value of " + e.getExpression() + " is : ", o, foo.convert(1+number.intValue()));
+
+ /*
+ * access a property
+ */
+ e = jexl.createExpression("foo.bar");
+ o = e.evaluate(jc);
+ out.print("value returned for the property 'bar' is : ", o, foo.get("bar"));
+
+ }
+
+ /**
+ * Helper example class.
+ */
+ public static class Foo {
+ /**
+ * Gets foo.
+ * @return a string.
+ */
+ public String getFoo() {
+ return "This is from getFoo()";
+ }
+
+ /**
+ * Gets an arbitrary property.
+ * @param arg property name.
+ * @return arg prefixed with 'This is the property '.
+ */
+ public String get(String arg) {
+ return "This is the property " + arg;
+ }
+
+ /**
+ * Gets a string from the argument.
+ * @param i a long.
+ * @return The argument prefixed with 'The value is : '
+ */
+ public String convert(long i) {
+ return "The value is : " + i;
+ }
+ }
+
+
+ /**
+ * Unit test entry point.
+ * @throws Exception
+ */
+ public void testExample() throws Exception {
+ example(Output.JUNIT);
+ }
+
+ /**
+ * Command line entry point.
+ * @param args command line arguments
+ * @throws Exception cos jexl does.
+ */
+ public static void main(String[] args) throws Exception {
+ example(Output.SYSTEM);
+ }
+}
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/examples/Output.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/examples/Output.java
new file mode 100644
index 0000000..c9553f0
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/examples/Output.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2.examples;
+import junit.framework.TestCase;
+
+/**
+ * Abstracts using a test within Junit or through a main method.
+ */
+public abstract class Output {
+ /**
+ * Creates an output using System.out.
+ */
+ private Output() {
+ // nothing to do
+ }
+
+ /**
+ * Outputs the actual and value or checks the actual equals the expected value.
+ * @param expr the message to output
+ * @param actual the actual value to output
+ * @param expected the expected value
+ */
+ public abstract void print(String expr, Object actual, Object expected);
+
+ /**
+ * The output instance for Junit TestCase calling assertEquals.
+ */
+ public static final Output JUNIT = new Output() {
+ @Override
+ public void print(String expr, Object actual, Object expected) {
+ TestCase.assertEquals(expr, expected, actual);
+ }
+ };
+
+
+ /**
+ * The output instance for the general outputing to System.out.
+ */
+ public static final Output SYSTEM = new Output() {
+ @Override
+ public void print(String expr, Object actual, Object expected) {
+ System.out.print(expr);
+ System.out.println(actual);
+ }
+ };
+}
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/internal/introspection/DiscoveryTest.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/internal/introspection/DiscoveryTest.java
new file mode 100644
index 0000000..252f72e
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/internal/introspection/DiscoveryTest.java
@@ -0,0 +1,221 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2.internal.introspection;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+import org.apache.commons.jexl2.JexlEngine;
+import org.apache.commons.jexl2.JexlTestCase;
+import org.apache.commons.jexl2.introspection.Uberspect;
+
+import org.apache.commons.jexl2.internal.Introspector;
+import org.apache.commons.jexl2.internal.AbstractExecutor;
+import org.apache.commons.jexl2.internal.PropertyGetExecutor;
+import org.apache.commons.jexl2.internal.PropertySetExecutor;
+import org.apache.commons.jexl2.internal.DuckGetExecutor;
+import org.apache.commons.jexl2.internal.DuckSetExecutor;
+import org.apache.commons.jexl2.internal.ListGetExecutor;
+import org.apache.commons.jexl2.internal.ListSetExecutor;
+import org.apache.commons.jexl2.internal.MapGetExecutor;
+import org.apache.commons.jexl2.internal.MapSetExecutor;
+
+/**
+ * Tests for checking introspection discovery.
+ *
+ * @since 2.0
+ */
+public class DiscoveryTest extends JexlTestCase {
+
+ public static class Duck {
+ private String value;
+ private String eulav;
+ public Duck(String v, String e) {
+ value = v;
+ eulav = e;
+ }
+ public String get(String prop) {
+ if ("value".equals(prop)) {
+ return value;
+ }
+ if ("eulav".equals(prop)) {
+ return eulav;
+ }
+ return "no such property";
+ }
+ public void set(String prop, String v) {
+ if ("value".equals(prop)) {
+ value = v;
+ } else if ("eulav".equals(prop)) {
+ eulav = v;
+ }
+ }
+ }
+
+ public static class Bean {
+ private String value;
+ private String eulav;
+ private boolean flag;
+ public Bean(String v, String e) {
+ value = v;
+ eulav = e;
+ flag = true;
+ }
+ public String getValue() {
+ return value;
+ }
+ public void setValue(String v) {
+ value = v;
+ }
+ public String getEulav() {
+ return eulav;
+ }
+ public void setEulav(String v) {
+ eulav = v;
+ }
+ public boolean isFlag() {
+ return flag;
+ }
+ public void setFlag(boolean f) {
+ flag = f;
+ }
+ }
+
+
+ public void testBeanIntrospection() throws Exception {
+ Uberspect uber = JexlEngine.getUberspect(null);
+ Introspector intro = (Introspector) uber;
+ Bean bean = new Bean("JEXL", "LXEJ");
+
+ AbstractExecutor.Get get = intro.getGetExecutor(bean, "value");
+ AbstractExecutor.Set set = intro.getSetExecutor(bean, "value", "foo");
+ assertTrue("bean property getter", get instanceof PropertyGetExecutor);
+ assertTrue("bean property setter", set instanceof PropertySetExecutor);
+ // introspector and uberspect should return same result
+ assertEquals(get, uber.getPropertyGet(bean, "value", null));
+ assertEquals(set, uber.getPropertySet(bean, "value", "foo", null));
+ // different property should return different setter/getter
+ assertFalse(get.equals(intro.getGetExecutor(bean, "eulav")));
+ assertFalse(set.equals(intro.getSetExecutor(bean, "eulav", "foo")));
+ // setter returns argument
+ Object bar = set.execute(bean, "bar");
+ assertEquals("bar", bar);
+ // getter should return last value
+ assertEquals("bar", get.execute(bean));
+ // tryExecute should succeed on same property
+ Object quux = set.tryExecute(bean, "value", "quux");
+ assertEquals("quux", quux);
+ assertEquals("quux", get.execute(bean));
+ // tryExecute should fail on different property
+ assertEquals(AbstractExecutor.TRY_FAILED, set.tryExecute(bean, "eulav", "nope"));
+
+ }
+
+ public void testDuckIntrospection() throws Exception {
+ Uberspect uber = JexlEngine.getUberspect(null);
+ Introspector intro = (Introspector) uber;
+ Duck duck = new Duck("JEXL", "LXEJ");
+
+ AbstractExecutor.Get get = intro.getGetExecutor(duck, "value");
+ AbstractExecutor.Set set = intro.getSetExecutor(duck, "value", "foo");
+ assertTrue("duck property getter", get instanceof DuckGetExecutor);
+ assertTrue("duck property setter", set instanceof DuckSetExecutor);
+ // introspector and uberspect should return same result
+ assertEquals(get, uber.getPropertyGet(duck, "value", null));
+ assertEquals(set, uber.getPropertySet(duck, "value", "foo", null));
+ // different property should return different setter/getter
+ assertFalse(get.equals(intro.getGetExecutor(duck, "eulav")));
+ assertFalse(set.equals(intro.getSetExecutor(duck, "eulav", "foo")));
+ // setter returns argument
+ Object bar = set.execute(duck, "bar");
+ assertEquals("bar", bar);
+ // getter should return last value
+ assertEquals("bar", get.execute(duck));
+ // tryExecute should succeed on same property
+ Object quux = set.tryExecute(duck, "value", "quux");
+ assertEquals("quux", quux);
+ assertEquals("quux", get.execute(duck));
+ // tryExecute should fail on different property
+ assertEquals(AbstractExecutor.TRY_FAILED, set.tryExecute(duck, "eulav", "nope"));
+ }
+
+ public void testListIntrospection() throws Exception {
+ Uberspect uber = JexlEngine.getUberspect(null);
+ Introspector intro = (Introspector) uber;
+ List<Object> list = new ArrayList<Object>();
+ list.add("LIST");
+ list.add("TSIL");
+
+ AbstractExecutor.Get get = intro.getGetExecutor(list, Integer.valueOf(1));
+ AbstractExecutor.Set set = intro.getSetExecutor(list, Integer.valueOf(1), "foo");
+ assertTrue("list property getter", get instanceof ListGetExecutor);
+ assertTrue("list property setter", set instanceof ListSetExecutor);
+ // introspector and uberspect should return same result
+ assertEquals(get, uber.getPropertyGet(list, Integer.valueOf(1), null));
+ assertEquals(set, uber.getPropertySet(list, Integer.valueOf(1), "foo", null));
+ // different property should return different setter/getter
+ assertFalse(get.equals(intro.getGetExecutor(list, Integer.valueOf(0))));
+ assertFalse(get.equals(intro.getSetExecutor(list, Integer.valueOf(0), "foo")));
+ // setter returns argument
+ Object bar = set.execute(list, "bar");
+ assertEquals("bar", bar);
+ // getter should return last value
+ assertEquals("bar", get.execute(list));
+ // tryExecute should succeed on integer property
+ Object quux = set.tryExecute(list, Integer.valueOf(1), "quux");
+ assertEquals("quux", quux);
+ // getter should return last value
+ assertEquals("quux", get.execute(list));
+ // tryExecute should fail on non-integer property class
+ assertEquals(AbstractExecutor.TRY_FAILED, set.tryExecute(list, "eulav", "nope"));
+ }
+
+ public void testMapIntrospection() throws Exception {
+ Uberspect uber = JexlEngine.getUberspect(null);
+ Introspector intro = (Introspector) uber;
+ Map<String, Object> map = new HashMap<String, Object>();
+ map.put("value", "MAP");
+ map.put("eulav", "PAM");
+
+ AbstractExecutor.Get get = intro.getGetExecutor(map, "value");
+ AbstractExecutor.Set set = intro.getSetExecutor(map, "value", "foo");
+ assertTrue("map property getter", get instanceof MapGetExecutor);
+ assertTrue("map property setter", set instanceof MapSetExecutor);
+ // introspector and uberspect should return same result
+ assertEquals(get, uber.getPropertyGet(map, "value", null));
+ assertEquals(set, uber.getPropertySet(map, "value", "foo", null));
+ // different property should return different setter/getter
+ assertFalse(get.equals(intro.getGetExecutor(map, "eulav")));
+ assertFalse(get.equals(intro.getSetExecutor(map, "eulav", "foo")));
+ // setter returns argument
+ Object bar = set.execute(map, "bar");
+ assertEquals("bar", bar);
+ // getter should return last value
+ assertEquals("bar", get.execute(map));
+ // tryExecute should succeed on same property class
+ Object quux = set.tryExecute(map, "value", "quux");
+ assertEquals("quux", quux);
+ // getter should return last value
+ assertEquals("quux", get.execute(map));
+ // tryExecute should fail on different property class
+ assertEquals(AbstractExecutor.TRY_FAILED, set.tryExecute(map, Integer.valueOf(1), "nope"));
+ }
+
+}
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/internal/introspection/MethodKeyTest.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/internal/introspection/MethodKeyTest.java
new file mode 100644
index 0000000..545c307
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/internal/introspection/MethodKeyTest.java
@@ -0,0 +1,220 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2.internal.introspection;
+import org.apache.commons.jexl2.internal.introspection.MethodKey;
+import org.apache.commons.jexl2.internal.introspection.ClassMap;
+import junit.framework.TestCase;
+/**
+ * Checks the CacheMap.MethodKey implementation
+ */
+public class MethodKeyTest extends TestCase {
+ // A set of classes (most of them primitives)
+ private static final Class<?>[] PRIMS = {
+ Boolean.TYPE,
+ Byte.TYPE,
+ Character.TYPE,
+ Double.TYPE,
+ Float.TYPE,
+ Integer.TYPE,
+ Long.TYPE,
+ Short.TYPE,
+ String.class,
+ java.util.Date.class
+ };
+
+ // A set of instances corresponding to the classes
+ private static final Object[] ARGS = {
+ new Boolean(true),
+ new Byte((byte) 1),
+ new Character('2'),
+ new Double(4d),
+ new Float(8f),
+ new Integer(16),
+ new Long(32l),
+ new Short((short)64),
+ "foobar",
+ new java.util.Date()
+ };
+
+ // A set of (pseudo) method names
+ private static final String[] METHODS = {
+ "plus",
+ "minus",
+ "execute",
+ "activate",
+ "perform",
+ "apply",
+ "invoke",
+ "executeAction",
+ "activateAction",
+ "performAction",
+ "applyAction",
+ "invokeAction",
+ "executeFunctor",
+ "activateFunctor",
+ "performFunctor",
+ "applyFunctor",
+ "invokeFunctor",
+ "executeIt",
+ "activateIt",
+ "performIt",
+ "applyIt",
+ "invokeIt"
+ };
+
+ /** from key to string */
+ private static final java.util.Map< MethodKey, String> byKey;
+ /** form string to key */
+ private static final java.util.Map<String,MethodKey> byString;
+ /** the list of keys we generated & test against */
+ private static final MethodKey[] keyList;
+
+ /** Creates & inserts a key into the byKey & byString map */
+ private static void setUpKey(String name, Class<?>[] parms) {
+ MethodKey key = new MethodKey(name, parms);
+ String str = key.toString();
+ byKey.put(key, str);
+ byString.put(str, key);
+
+ }
+
+ /** Generate a list of method*(prims*), method(prims*, prims*), method*(prims*,prims*,prims*) */
+ static {
+ byKey = new java.util.HashMap< MethodKey, String>();
+ byString = new java.util.HashMap<String,MethodKey>();
+ for (int m = 0; m < METHODS.length; ++m) {
+ String method = METHODS[m];
+ for (int p0 = 0; p0 < PRIMS.length; ++p0) {
+ Class<?>[] arg0 = {PRIMS[p0]};
+ setUpKey(method, arg0);
+ for (int p1 = 0; p1 < PRIMS.length; ++p1) {
+ Class<?>[] arg1 = {PRIMS[p0], PRIMS[p1]};
+ setUpKey(method, arg1);
+ for (int p2 = 0; p2 < PRIMS.length; ++p2) {
+ Class<?>[] arg2 = {PRIMS[p0], PRIMS[p1], PRIMS[p2]};
+ setUpKey(method, arg2);
+ }
+ }
+ }
+ }
+ keyList = byKey.keySet().toArray(new MethodKey[byKey.size()]);
+ }
+
+ /** Builds a string key */
+ String makeStringKey(String method, Class<?>... params) {
+ StringBuilder builder = new StringBuilder(method);
+ for(int p = 0; p < params.length; ++p) {
+ builder.append(ClassMap.MethodCache.primitiveClass(params[p]).getName());
+ }
+ return builder.toString();
+ }
+
+ /** Checks that a string key does exist */
+ void checkStringKey(String method, Class<?>... params) {
+ String key = makeStringKey(method, params);
+ MethodKey out = byString.get(key);
+ assertTrue(out != null);
+ }
+
+ /** Builds a method key */
+ MethodKey makeKey(String method, Class<?>... params) {
+ return new MethodKey(method, params);
+ }
+
+ /** Checks that a method key exists */
+ void checkKey(String method, Class<?>... params) {
+ MethodKey key = makeKey(method, params);
+ String out = byKey.get(key);
+ assertTrue(out != null);
+ }
+
+ public void testObjectKey() throws Exception {
+ for(int k = 0; k < keyList.length; ++k) {
+ MethodKey ctl = keyList[k];
+ MethodKey key = makeKey(ctl.getMethod(), ctl.getParameters());
+ String out = byKey.get(key);
+ assertTrue(out != null);
+ assertTrue(ctl.toString() + " != " + out, ctl.toString().equals(out));
+ }
+
+ }
+
+ public void testStringKey() throws Exception {
+ for(int k = 0; k < keyList.length; ++k) {
+ MethodKey ctl = keyList[k];
+ String key = makeStringKey(ctl.getMethod(), ctl.getParameters());
+ MethodKey out = byString.get(key);
+ assertTrue(out != null);
+ assertTrue(ctl.toString() + " != " + key, ctl.equals(out));
+ }
+
+ }
+
+ private static final int LOOP = 3;//00;
+
+ public void testPerfKey() throws Exception {
+ for(int l = 0; l < LOOP; ++l)
+ for(int k = 0; k < keyList.length; ++k) {
+ MethodKey ctl = keyList[k];
+ MethodKey key = makeKey(ctl.getMethod(), ctl.getParameters());
+ String out = byKey.get(key);
+ assertTrue(out != null);
+ }
+ }
+
+ public void testPerfString() throws Exception {
+ for(int l = 0; l < LOOP; ++l)
+ for(int k = 0; k < keyList.length; ++k) {
+ MethodKey ctl = keyList[k];
+ String key = makeStringKey(ctl.getMethod(), ctl.getParameters());
+ MethodKey out = byString.get(key);
+ assertTrue(out != null);
+ }
+ }
+
+ public void testPerfKey2() throws Exception {
+ for(int l = 0; l < LOOP; ++l)
+ for (int m = 0; m < METHODS.length; ++m) {
+ String method = METHODS[m];
+ for (int p0 = 0; p0 < ARGS.length; ++p0) {
+ checkKey(method, ARGS[p0].getClass());
+ for (int p1 = 0; p1 < ARGS.length; ++p1) {
+ checkKey(method, ARGS[p0].getClass(), ARGS[p1].getClass());
+ for (int p2 = 0; p2 < ARGS.length; ++p2) {
+ checkKey(method, ARGS[p0].getClass(), ARGS[p1].getClass(), ARGS[p2].getClass());
+ }
+ }
+ }
+ }
+ }
+
+ public void testPerfStringKey2() throws Exception {
+ for(int l = 0; l < LOOP; ++l)
+ for (int m = 0; m < METHODS.length; ++m) {
+ String method = METHODS[m];
+ for (int p0 = 0; p0 < ARGS.length; ++p0) {
+ checkStringKey(method, ARGS[p0].getClass());
+ for (int p1 = 0; p1 < ARGS.length; ++p1) {
+ checkStringKey(method, ARGS[p0].getClass(), ARGS[p1].getClass());
+ for (int p2 = 0; p2 < ARGS.length; ++p2) {
+ checkStringKey(method, ARGS[p0].getClass(), ARGS[p1].getClass(), ARGS[p2].getClass());
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/junit/Asserter.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/junit/Asserter.java
new file mode 100644
index 0000000..4b086fd
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/junit/Asserter.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl2.junit;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.Assert;
+
+import org.apache.commons.jexl2.Expression;
+import org.apache.commons.jexl2.JexlContext;
+import org.apache.commons.jexl2.MapContext;
+import org.apache.commons.jexl2.JexlEngine;
+
+/**
+ * A utility class for performing JUnit based assertions using Jexl
+ * expressions. This class can make it easier to do unit tests using
+ * Jexl navigation expressions.
+ *
+ * @since 1.0
+ * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
+ * @version $Revision$
+ */
+public class Asserter extends Assert {
+ /** variables used during asserts. */
+ private final Map<String, Object> variables = new HashMap<String, Object>();
+ /** context to use during asserts. */
+ private final JexlContext context = new MapContext(variables);
+
+ /** Jexl engine to use during Asserts. */
+ private final JexlEngine engine;
+
+ /**
+ *
+ * Create an asserter.
+ */
+ public Asserter(JexlEngine jexl) {
+ engine = jexl;
+ }
+
+
+ /**
+ * Performs an assertion that the value of the given Jexl expression
+ * evaluates to the given expected value.
+ *
+ * @param expression is the Jexl expression to evaluate
+ * @param expected is the expected value of the expression
+ * @throws Exception if the expression could not be evaluationed or an assertion
+ * fails
+ */
+ public void assertExpression(String expression, Object expected) throws Exception {
+ Expression exp = engine.createExpression(expression);
+ Object value = exp.evaluate(context);
+
+ assertEquals("expression: " + expression, expected, value);
+ }
+
+ /**
+ * Puts a variable of a certain name in the context so that it can be used from
+ * assertion expressions.
+ *
+ * @param name variable name
+ * @param value variable value
+ */
+ public void setVariable(String name, Object value) {
+ variables.put(name, value);
+ }
+
+}
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/junit/AsserterTest.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/junit/AsserterTest.java
new file mode 100644
index 0000000..47a7222
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/junit/AsserterTest.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2.junit;
+
+import junit.framework.AssertionFailedError;
+
+import org.apache.commons.jexl2.JexlEngine;
+import org.apache.commons.jexl2.JexlTestCase;
+import org.apache.commons.jexl2.Foo;
+
+/**
+ * Simple testcases
+ *
+ * @since 1.0
+ * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class AsserterTest extends JexlTestCase {
+ public AsserterTest(String testName) {
+ super(testName);
+ }
+
+ public void testThis() throws Exception {
+ Asserter asserter = new Asserter(JEXL);
+ asserter.setVariable("this", new Foo());
+
+ asserter.assertExpression("this.get('abc')", "Repeat : abc");
+
+ try {
+ asserter.assertExpression("this.count", "Wrong Value");
+ fail("This method should have thrown an assertion exception");
+ }
+ catch (AssertionFailedError e) {
+ // it worked!
+ }
+ }
+
+ public void testVariable() throws Exception {
+ JexlEngine jexl = new JexlEngine();
+ jexl.setSilent(true);
+ Asserter asserter = new Asserter(jexl);
+ asserter.setVariable("foo", new Foo());
+ asserter.setVariable("person", "James");
+
+ asserter.assertExpression("person", "James");
+ asserter.assertExpression("size(person)", new Integer(5));
+
+ asserter.assertExpression("foo.getCount()", new Integer(5));
+ asserter.assertExpression("foo.count", new Integer(5));
+
+ try {
+ asserter.assertExpression("bar.count", new Integer(5));
+ fail("This method should have thrown an assertion exception");
+ }
+ catch (AssertionFailedError e) {
+ // it worked!
+ }
+ }
+}
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/junit/package.html b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/junit/package.html
new file mode 100644
index 0000000..7309e67
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/junit/package.html
@@ -0,0 +1,53 @@
+<html>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+-->
+ <head>
+ <title>Package Documentation for org.apache.commons.jexl2.junit Package</title>
+ </head>
+ <body bgcolor="white">
+ Using JEXL expressions in JUnit assertions.
+ <br/><br/>
+ <p>
+ <ul>
+ <li><a href="#intro">Introduction</a></li>
+ </ul>
+ </p>
+ <h2><a name="intro">Introduction</a></h2>
+ <p>
+ This package only contains one class, Asserter, which
+ allows you to use a JEXL expression in a JUnit assertion.
+ The following example demonstrates the use of the Asserter
+ class. An instance is created, and the internal JexlContext
+ is populated via calls to setVariable(). Calls to
+ assertExpression() succeed if the expression evaluates to
+ the value of the second parameter, otherwise an
+ AssertionFailedException is thrown.
+ </p>
+
+ <pre>
+ Asserter asserter = new Asserter();
+ asserter.setVariable("foo", new Foo());
+ asserter.setVariable("person", "James");
+
+ asserter.assertExpression("person", "James");
+ asserter.assertExpression("size(person)", new Integer(5));
+
+ asserter.assertExpression("foo.getCount()", new Integer(5));
+ asserter.assertExpression("foo.count", new Integer(5));
+ </pre>
+</body>
+</html>
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/parser/ParserTest.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/parser/ParserTest.java
new file mode 100644
index 0000000..cb8ea15
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/parser/ParserTest.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl2.parser;
+
+import java.io.StringReader;
+
+import junit.framework.TestCase;
+
+/**
+ * @since 1.0
+ *
+ */
+public class ParserTest extends TestCase
+{
+ public ParserTest(String testName)
+ {
+ super(testName);
+ }
+
+ /**
+ * parse test : see if we can parse a little script
+ */
+ public void testParse1()
+ throws Exception
+ {
+ Parser parser = new Parser(new StringReader(";"));
+
+ SimpleNode sn = parser.parse(new StringReader("foo = 1;"), null);
+ assertNotNull("parsed node is null", sn);
+ }
+
+ public void testParse2()
+ throws Exception
+ {
+ Parser parser = new Parser(new StringReader(";"));
+
+ SimpleNode sn = parser.parse(new StringReader("foo = \"bar\";"), null);
+ assertNotNull("parsed node is null", sn);
+
+ sn = parser.parse(new StringReader("foo = 'bar';"), null);
+ assertNotNull("parsed node is null", sn);
+ }
+}
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/scripting/JexlScriptEngineOptionalTest.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/scripting/JexlScriptEngineOptionalTest.java
new file mode 100644
index 0000000..424d154
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/scripting/JexlScriptEngineOptionalTest.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.commons.jexl2.scripting;
+
+import java.io.StringWriter;
+import javax.script.Compilable;
+import javax.script.CompiledScript;
+
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+import junit.framework.TestCase;
+
+public class JexlScriptEngineOptionalTest extends TestCase {
+ private final JexlScriptEngineFactory factory = new JexlScriptEngineFactory();
+ private final ScriptEngineManager manager = new ScriptEngineManager();
+ private final ScriptEngine engine = manager.getEngineByName("jexl");
+
+ public void testOutput() throws Exception {
+ String output = factory.getOutputStatement("foo\u00a9bar");
+ assertEquals("JEXL.out.print('foo\\u00a9bar')", output);
+ // redirect output to capture evaluation result
+ final StringWriter outContent = new StringWriter();
+ engine.getContext().setWriter(outContent);
+ engine.eval(output);
+ assertEquals("foo\u00a9bar", outContent.toString());
+ }
+
+ public void testError() throws Exception {
+ String error = "JEXL.err.print('ERROR')";
+ // redirect error to capture evaluation result
+ final StringWriter outContent = new StringWriter();
+ engine.getContext().setErrorWriter(outContent);
+ engine.eval(error);
+ assertEquals("ERROR", outContent.toString());
+ }
+
+ public void testCompilable() throws Exception {
+ assertTrue("Engine should implement Compilable", engine instanceof Compilable);
+ Compilable cengine = (Compilable) engine;
+ CompiledScript script = cengine.compile("40 + 2");
+ assertEquals(Integer.valueOf(42), script.eval());
+ assertEquals(Integer.valueOf(42), script.eval());
+ }
+}
diff --git a/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/scripting/JexlScriptEngineTest.java b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/scripting/JexlScriptEngineTest.java
new file mode 100644
index 0000000..fc7659f
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/java/org/apache/commons/jexl2/scripting/JexlScriptEngineTest.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.commons.jexl2.scripting;
+
+import java.io.Reader;
+import java.util.Arrays;
+import java.util.Map;
+
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+import junit.framework.TestCase;
+
+public class JexlScriptEngineTest extends TestCase {
+
+ public void testScriptEngineFactory() throws Exception {
+ JexlScriptEngineFactory factory = new JexlScriptEngineFactory();
+ assertEquals("JEXL Engine", factory.getParameter(ScriptEngine.ENGINE));
+ assertEquals("1.0", factory.getParameter(ScriptEngine.ENGINE_VERSION));
+ assertEquals("JEXL", factory.getParameter(ScriptEngine.LANGUAGE));
+ assertEquals("2.0", factory.getParameter(ScriptEngine.LANGUAGE_VERSION));
+ assertEquals(Arrays.asList("JEXL", "Jexl", "jexl"), factory.getParameter(ScriptEngine.NAME));
+ assertNull(factory.getParameter("THREADING"));
+
+ assertEquals(Arrays.asList("jexl"), factory.getExtensions());
+ assertEquals(Arrays.asList("application/x-jexl"), factory.getMimeTypes());
+
+ assertEquals("42;", factory.getProgram(new String[]{"42"}));
+ assertEquals("str.substring(3,4)", factory.getMethodCallSyntax("str", "substring", new String[]{"3", "4"}));
+ }
+
+ public void testScripting() throws Exception {
+ ScriptEngineManager manager = new ScriptEngineManager();
+ assertNotNull("Manager should not be null", manager);
+ ScriptEngine engine = manager.getEngineByName("jexl");
+ assertNotNull("Engine should not be null (name)", engine);
+ engine = manager.getEngineByExtension("jexl");
+ assertNotNull("Engine should not be null (ext)", engine);
+ final Integer initialValue = Integer.valueOf(123);
+ assertEquals(initialValue,engine.eval("123"));
+ assertEquals(initialValue,engine.eval("0;123"));// multiple statements
+ long time1 = System.currentTimeMillis();
+ Long time2 = (Long) engine.eval(
+ "sys=context.class.forName(\"java.lang.System\");"
+ +"now=sys.currentTimeMillis();"
+ );
+ assertTrue("Must take some time to process this",time1 <= time2.longValue());
+ engine.put("value", initialValue);
+ assertEquals(initialValue,engine.get("value"));
+ final Integer newValue = Integer.valueOf(124);
+ assertEquals(newValue,engine.eval("old=value;value=value+1"));
+ assertEquals(initialValue,engine.get("old"));
+ assertEquals(newValue,engine.get("value"));
+ assertEquals(engine.getContext(),engine.get(JexlScriptEngine.CONTEXT_KEY));
+ // Check behaviour of JEXL object
+ assertEquals(engine.getContext().getReader(),engine.eval("JEXL.in"));
+ assertEquals(engine.getContext().getWriter(),engine.eval("JEXL.out"));
+ assertEquals(engine.getContext().getErrorWriter(),engine.eval("JEXL.err"));
+ assertEquals(System.class,engine.eval("JEXL.System"));
+ }
+
+ public void testNulls() throws Exception {
+ ScriptEngineManager manager = new ScriptEngineManager();
+ assertNotNull("Manager should not be null", manager);
+ ScriptEngine engine = manager.getEngineByName("jexl");
+ assertNotNull("Engine should not be null (name)", engine);
+ try {
+ engine.eval((String)null);
+ fail("Should have caused NPE");
+ } catch (NullPointerException e) {
+ // NOOP
+ }
+ try {
+ engine.eval((Reader)null);
+ fail("Should have caused NPE");
+ } catch (NullPointerException e) {
+ // NOOP
+ }
+ }
+
+ public void testEngineNames() throws Exception {
+ ScriptEngineManager manager = new ScriptEngineManager();
+ assertNotNull("Manager should not be null", manager);
+ ScriptEngine engine = manager.getEngineByName("JEXL");
+ assertNotNull("Engine should not be null (JEXL)", engine);
+ engine = manager.getEngineByName("Jexl");
+ assertNotNull("Engine should not be null (Jexl)", engine);
+ engine = manager.getEngineByName("jexl");
+ assertNotNull("Engine should not be null (jexl)", engine);
+ }
+
+ public void testScopes() throws Exception {
+ ScriptEngineManager manager = new ScriptEngineManager();
+ assertNotNull("Manager should not be null", manager);
+ ScriptEngine engine = manager.getEngineByName("JEXL");
+ assertNotNull("Engine should not be null (JEXL)", engine);
+ manager.put("global",Integer.valueOf(1));
+ engine.put("local", Integer.valueOf(10));
+ manager.put("both",Integer.valueOf(7));
+ engine.put("both", Integer.valueOf(7));
+ engine.eval("local=local+1");
+ engine.eval("global=global+1");
+ engine.eval("both=both+1"); // should update engine value only
+ engine.eval("newvar=42;");
+ assertEquals(Integer.valueOf(2),manager.get("global"));
+ assertEquals(Integer.valueOf(11),engine.get("local"));
+ assertEquals(Integer.valueOf(7),manager.get("both"));
+ assertEquals(Integer.valueOf(8),engine.get("both"));
+ assertEquals(Integer.valueOf(42),engine.get("newvar"));
+ assertNull(manager.get("newvar"));
+ }
+
+ public void testDottedNames() throws Exception {
+ ScriptEngineManager manager = new ScriptEngineManager();
+ assertNotNull("Manager should not be null", manager);
+ ScriptEngine engine = manager.getEngineByName("JEXL");
+ assertNotNull("Engine should not be null (JEXL)", engine);
+ engine.eval("this.is.a.test=null");
+ assertNull(engine.get("this.is.a.test"));
+ assertEquals(Boolean.TRUE, engine.eval("empty(this.is.a.test)"));
+ final Object mymap = engine.eval("testmap={ 'key1' : 'value1', 'key2' : 'value2' }");
+ assertTrue(mymap instanceof Map<?, ?>);
+ assertEquals(2,((Map<?, ?>)mymap).size());
+ }
+}
diff --git a/COMMONS_JEXL_2_0/src/test/scripts/test1.jexl b/COMMONS_JEXL_2_0/src/test/scripts/test1.jexl
new file mode 100644
index 0000000..c21f6bb
--- /dev/null
+++ b/COMMONS_JEXL_2_0/src/test/scripts/test1.jexl
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+// This tests for JEXL-47. AL header above tests for block comments.
+
+##
+## This is a test script
+##
+if (out != null) out.println('Starting test script');
+x = 1;
+y = 2;
+result = x * y + 5;
+if (out != null) out.println("The result is " + result);
+## return the result.
+result; // JEXL-44 should ignore "quotes" here
+
+/*
+ Trailing comments are also ignored
+*/
\ No newline at end of file
diff --git a/COMMONS_JEXL_2_0/xdocs/building.xml b/COMMONS_JEXL_2_0/xdocs/building.xml
new file mode 100644
index 0000000..591757a
--- /dev/null
+++ b/COMMONS_JEXL_2_0/xdocs/building.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You 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.
+-->
+<document>
+ <properties>
+ <title>Building Commons JEXL</title>
+ <author email="dev@commons.apache.org">Commons Documentation Team</author>
+ </properties>
+<body>
+<!-- ================================================== -->
+<section name="Overview">
+<p>
+ Commons JEXL uses <a href="http://maven.apache.org">Maven 2.2</a>. The source is
+ <a href="http://svn.apache.org/repos/asf/commons/proper/jexl/trunk/">here</a>.
+</p>
+</section>
+<!-- ================================================== -->
+<section name="Maven Goals">
+The following goals are available.
+<ul>
+<li>mvn clean - clean up</li>
+<li>mvn test - compile and run the unit tests</li>
+<li>mvn site - create the documentation</li>
+<li>mvn package - build the jar</li>
+<li>mvn install - build the jar and install in local maven repository</li>
+</ul>
+</section>
+</body>
+</document>
diff --git a/COMMONS_JEXL_2_0/xdocs/changes.xml b/COMMONS_JEXL_2_0/xdocs/changes.xml
new file mode 100644
index 0000000..223454f
--- /dev/null
+++ b/COMMONS_JEXL_2_0/xdocs/changes.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+ -->
+
+<document>
+ <properties>
+ <title>Changes</title>
+ <author email="dev@commons.apache.org">Commons Developers</author>
+ </properties>
+ <body>
+ <release version="2.0" date="unreleased">
+ <action dev="henrib" type="add" issue="JEXL-27" due-to="Weikuo Liaw">Bean-ish & ant-ish like assignment</action>
+ <action dev="henrib" type="add" issue="JEXL-19" due-to="Jesse Glick">Ternary operator support</action>
+ <action dev="henrib" type="add" issue="JEXL-46" due-to="Alfred Reibenschuh">adding Perl-like regular-expression operators</action>
+ <action dev="henrib" type="add" issue="JEXL-41" due-to="Alejandro Torras">Support for ${...} and #{...} expressions</action>
+ <action dev="henrib" type="add" issue="JEXL-15" due-to="Paul Libbrecht">User definable functions</action>
+ <action dev="sebb" type="add" issue="JEXL-63">JSR-223 support</action>
+ <action dev="henrib" type="update" issue="JEXL-10" due-to="Paul Libbrecht">Make possible checking for unresolved variables</action>
+ <action dev="henrib" type="update" issue="JEXL-11" due-to="Paul Libbrecht">Don"t make null convertible into anything</action>
+ <action dev="henrib" type="fix" issue="JEXL-47" due-to="sebb">Allow single-line comments with //</action>
+ <action dev="henrib" type="fix" issue="JEXL-44" due-to="sebb">Comments don"t allow double-quotes</action>
+ <action dev="henrib" type="add" issue="JEXL-71" due-to="sebb">Array literal syntax is not supported</action>
+ <action dev="dion" type="fix" issue="JEXL-17" due-to="Nestor Urquiza">allowing quote escaping</action>
+ <action dev="dion" type="fix" issue="JEXL-25" due-to="Marek Lewczuk">Call method with varargs</action>
+ <action dev="dion" type="fix" issue="JEXL-32" due-to="Kedar Dave">BigDecimal values are treated as Long values which results in loss of precision</action>
+ <action dev="dion" type="fix" issue="JEXL-33">Remove unnecessary throws Exception from various classes</action>
+ <action dev="henrib" type="fix" issue="JEXL-50" due-to="sebb">Div operator does not do integer division</action>
+ <action dev="henrib" type="fix" issue="JEXL-87" due-to="sebb">Inconsistent behaviour of arithmetical operations</action>
+ <action dev="henrib" type="fix" issue="JEXL-21" due-to="AC">operator overloading / hooks on operator processing</action>
+ <action dev="henrib" type="add">"new" operator support</action>
+ <action dev="henrib" type="add">Support Unicode escapes in string literals</action>
+ <action dev="henrib" type="update">Various performance enhancements & caches</action>
+ </release>
+ <release version="1.1.1-SNAPSHOT" date="unreleased">
+ <action dev="dion" type="update" issue="JEXL-23">Fix jdk1.3 only code that has crept into Jexl tests</action>
+ <action dev="dion" type="update" issue="JEXL-22" due-to="Randy H.">Allow unicode literals to be used</action>
+ </release>
+ <release version="1.1" date="2006-09-10">
+ <action dev="rahul" type="fix" issue="JEXL-17" due-to="Kohsuke Kawaguchi">Consistently throw ParseException in case of a parsing failure, not an Error.</action>
+ <action dev="dion" type="fix" issue="JEXL-3" due-to="Guido Anzuoni">Allow for static methods to be called on classes and not just objects.</action>
+ <action dev="dion" type="add">Added Script and ScriptFactory to allow scripts to be executed from text, files or a URL.</action>
+ <action dev="dion" type="add">Added implementation for bitwise operators: and, complement, or, xor.</action>
+ <action dev="dion" type="add">Added implementation for the foreach statement.</action>
+ <action dev="dion" type="add">Added implementation for the while statement.</action>
+ <action dev="dion" type="add">Added implementation for block statements, e.g. curly braces containing multiple statements.</action>
+ <action dev="dion" type="add">Added implementation for the if statement.</action>
+ <action dev="dion" type="fix" issue="JEXL-6">Unary minus was only working for integer values.</action>
+ <action dev="dion" type="update">Add @since tags to code so we can track API additions via javadoc</action>
+ <action dev="dion" type="add" issue="JEXL-4" due-to="Barry Lagerweij">Support assignment to variables</action>
+ <action dev="dion" type="fix" issue="JEXL-5">'abc'.substring(0,1+1) is empty (method matching problem)</action>
+ </release>
+ <release version="1.0" date="2004-09-07">
+ <action dev="dion" type="fix">Support ant-style properties</action>
+ </release>
+ <release version="1.0-RC1" date="2004-08-26">
+ <action dev="dion" type="fix" due-to="Geoff Waggott">Fix string concatenation broken for variables</action>
+ <action dev="dion" type="fix" issue="JEXL-12">Implement short circuit logic for boolean and/or</action>
+ <action dev="dion" type="add">Handle any size() method that returns an int</action>
+ <action dev="dion" type="fix" issue="JEXL-9">Can't issue .size() on java.util.Set</action>
+ </release>
+ </body>
+</document>
diff --git a/COMMONS_JEXL_2_0/xdocs/cvs-usage.xml b/COMMONS_JEXL_2_0/xdocs/cvs-usage.xml
new file mode 100644
index 0000000..b29ed50
--- /dev/null
+++ b/COMMONS_JEXL_2_0/xdocs/cvs-usage.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You 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.
+-->
+<document>
+ <properties>
+ <title>Source repository</title>
+ <author email="dev@commons.apache.org">Commons Documentation Team</author>
+ </properties>
+ <body>
+<!-- ================================================== -->
+<section name="Commons JEXL Source repository">
+<p>
+ Apache Commons JEXL is hosted on the Apache
+ <a href="http://subversion.tigris.org/">subversion</a> repository.
+</p>
+<p>
+ The project URL is:<br />
+ <code>http://svn.apache.org/repos/asf/commons/proper/jexl/trunk</code>
+</p>
+<p>
+ The best way to view the repository is via the
+ <a href="http://svn.apache.org/viewvc/commons/proper/jexl/trunk/">subversion viewer</a>.
+</p>
+<p>
+ The alternative is to use the
+ <a href="http://svn.apache.org/repos/asf/commons/proper/jexl/trunk/">native subversion</a> display.
+</p>
+</section>
+<!-- ================================================== -->
+</body>
+</document>
diff --git a/COMMONS_JEXL_2_0/xdocs/download_jexl.xml b/COMMONS_JEXL_2_0/xdocs/download_jexl.xml
new file mode 100644
index 0000000..c55a033
--- /dev/null
+++ b/COMMONS_JEXL_2_0/xdocs/download_jexl.xml
@@ -0,0 +1,145 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You 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.
+-->
+<!--
+ +======================================================================+
+ |**** ****|
+ |**** THIS FILE IS GENERATED BY THE COMMONS BUILD PLUGIN ****|
+ |**** DO NOT EDIT DIRECTLY ****|
+ |**** ****|
+ +======================================================================+
+ | TEMPLATE FILE: download-page-template.xml |
+ | commons-build-plugin/trunk/src/main/resources/commons-xdoc-templates |
+ +======================================================================+
+ | |
+ | 1) Re-generate using: mvn commons:download-page |
+ | |
+ | 2) Set the following properties in the component's pom: |
+ | - commons.componentid (required, alphabetic, lower case) |
+ | - commons.release.version (required) |
+ | - commons.binary.suffix (optional) |
+ | (defaults to "-bin", set to "" for pre-maven2 releases) |
+ | |
+ | 3) Example Properties |
+ | |
+ | <properties> |
+ | <commons.componentid>math</commons.componentid> |
+ | <commons.release.version>1.2</commons.release.version> |
+ | </properties> |
+ | |
+ +======================================================================+
+-->
+<document>
+ <properties>
+ <title>Download Commons JEXL</title>
+ <author email="dev@commons.apache.org">Commons Documentation Team</author>
+ </properties>
+ <body>
+ <section name="Download Commons JEXL">
+ <p>
+ We recommend you use a mirror to download our release
+ builds, but you <strong>must</strong> verify the integrity of
+ the downloaded files using signatures downloaded from our main
+ distribution directories. Recent releases (48 hours) may not yet
+ be available from the mirrors.
+ </p>
+
+ <p>
+ You are currently using <b>[preferred]</b>. If you
+ encounter a problem with this mirror, please select another
+ mirror. If all mirrors are failing, there are <i>backup</i>
+ mirrors (at the end of the mirrors list) that should be
+ available.
+ <br></br>
+ [if-any logo]<a href="[link]"><img align="right" src="[logo]" border="0"></img></a>[end]
+ </p>
+
+ <form action="[location]" method="get" id="SelectMirror">
+ <p>
+ Other mirrors:
+ <select name="Preferred">
+ [if-any http]
+ [for http]<option value="[http]">[http]</option>[end]
+ [end]
+ [if-any ftp]
+ [for ftp]<option value="[ftp]">[ftp]</option>[end]
+ [end]
+ [if-any backup]
+ [for backup]<option value="[backup]">[backup] (backup)</option>[end]
+ [end]
+ </select>
+ <input type="submit" value="Change"></input>
+ </p>
+ </form>
+
+ <p>
+ The <code>KEYS</code> link links to the code signing keys used to sign the product.
+ The <code>PGP</code> link downloads the OpenPGP compatible signature from our main site.
+ The <code>MD5</code> link downloads the checksum from the main site.
+ </p>
+
+ <p>
+ For more information concerning Commons JEXL, see the
+ <a href="index.html" class="name">Commons JEXL</a> web site.
+ </p>
+
+ <p>
+ <div class="links"><span class="link"><a href="http://www.apache.org/dist/commons/KEYS">KEYS</a></span></div>
+ <ul class="downloads">
+ <li class="group"><div class="links"><span class="label">Binary</span></div>
+ <ul>
+ <li class="download"><a href="[preferred]/commons/jexl/binaries/commons-jexl-2.0.tar.gz">2.0.tar.gz</a>
+ <ul class="attributes">
+ <li><span class="md5">[<a href="http://www.apache.org/dist/commons/jexl/binaries/commons-jexl-2.0.tar.gz.md5">md5</a>]</span>
+ <span class="pgp">[<a href="http://www.apache.org/dist/commons/jexl/binaries/commons-jexl-2.0.tar.gz.asc">pgp</a>]</span>
+ </li>
+ </ul>
+ </li>
+ <li class="download"><a href="[preferred]/commons/jexl/binaries/commons-jexl-2.0.zip">2.0.zip</a>
+ <ul class="attributes">
+ <li><span class="md5">[<a href="http://www.apache.org/dist/commons/jexl/binaries/commons-jexl-2.0.zip.md5">md5</a>]</span>
+ <span class="pgp">[<a href="http://www.apache.org/dist/commons/jexl/binaries/commons-jexl-2.0.zip.asc">pgp</a>]</span>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </li>
+ <li class="group"><div class="links"><span class="label">Source</span></div>
+ <ul>
+ <li class="download"><a href="[preferred]/commons/jexl/source/commons-jexl-2.0-src.tar.gz">2.0.tar.gz</a>
+ <ul class="attributes">
+ <li><span class="md5">[<a href="http://www.apache.org/dist/commons/jexl/source/commons-jexl-2.0-src.tar.gz.md5">md5</a>]</span>
+ <span class="pgp">[<a href="http://www.apache.org/dist/commons/jexl/source/commons-jexl-2.0-src.tar.gz.asc">pgp</a>]</span>
+ </li>
+ </ul>
+ </li>
+ <li class="download"><a href="[preferred]/commons/jexl/source/commons-jexl-2.0-src.zip">2.0.zip</a>
+ <ul class="attributes">
+ <li><span class="md5">[<a href="http://www.apache.org/dist/commons/jexl/source/commons-jexl-2.0-src.zip.md5">md5</a>]</span>
+ <span class="pgp">[<a href="http://www.apache.org/dist/commons/jexl/source/commons-jexl-2.0-src.zip.asc">pgp</a>]</span>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </li>
+ <li class="download"><a href="[preferred]/commons/jexl/">browse download area</a></li>
+ <li><a href="http://archive.apache.org/dist/commons/jexl/">archives...</a></li>
+ </ul>
+ </p>
+ </section>
+ </body>
+</document>
diff --git a/COMMONS_JEXL_2_0/xdocs/images/jexl-logo-white.png b/COMMONS_JEXL_2_0/xdocs/images/jexl-logo-white.png
new file mode 100644
index 0000000..3ad2943
--- /dev/null
+++ b/COMMONS_JEXL_2_0/xdocs/images/jexl-logo-white.png
Binary files differ
diff --git a/COMMONS_JEXL_2_0/xdocs/images/jexl-logo-white.xcf b/COMMONS_JEXL_2_0/xdocs/images/jexl-logo-white.xcf
new file mode 100644
index 0000000..3edf5c2
--- /dev/null
+++ b/COMMONS_JEXL_2_0/xdocs/images/jexl-logo-white.xcf
Binary files differ
diff --git a/COMMONS_JEXL_2_0/xdocs/index.xml b/COMMONS_JEXL_2_0/xdocs/index.xml
new file mode 100644
index 0000000..8916bc8
--- /dev/null
+++ b/COMMONS_JEXL_2_0/xdocs/index.xml
@@ -0,0 +1,202 @@
+<?xml version="1.0"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+-->
+
+<document>
+ <properties>
+ <title>Commons JEXL Overview</title>
+ </properties>
+
+ <body>
+ <section name="Java Expression Language (JEXL)">
+ <p>
+ JEXL is a library intended to facilitate the implementation of dynamic and scripting features in
+ applications and frameworks.
+ </p>
+ <p>
+ It is a small footprint "glueing" API
+ - the <a href="apidocs/org/apache/commons/jexl2/package-summary.html#usage">core features</a> fit in
+ 3 classes and 10 methods - that can be used in various conditions:
+ <ul>
+ <li>Module or component configuration:
+ <ul>
+ <li>Your application has configuration files (eventually generated by a design module)
+ consumed by the end-user module that would benefit from variables and expressions.
+ </li>
+ <li>When it would be convenient to use IOC but overall complexity doesn't require
+ (or can't depend upon) a full-blown library (Spring, Guice...).
+ </li>
+ </ul>
+ </li>
+ <li>Loose-coupling of interfaces and implementations or duck-typing:
+ <ul>
+ <li>You have optional classes that your code cant consider as compilation dependencies.</li>
+ <li>You have to integrate and call "legacy" code or use components that you dont want to
+ strongly depend upon.</li>
+ </ul>
+ </li>
+ <li>Basic scripting features:
+ <ul><li>Your application lets (advanced) users evaluate or define some simple expressions
+ like computation formulas.</li></ul>
+ </li>
+ <li>Simple template capabilities:
+ <ul><li>Your application has basic template requirements and JSPs or
+ Velocity would be overkill or too inconvenient to deploy.</li></ul>
+ </li>
+ </ul>
+ </p>
+ <p>
+ Its name stands for Java EXpression Language, a simple expression language inspired by Jakarta
+ Velocity and the Expression Language defined in the JavaServer Pages Standard Tag Library version 1.1 (JSTL)
+ and JavaServer Pages version 2.0 (JSP). JEXL 2.0 adds features inspired by
+ <a href="http://java.sun.com/products/jsp/reference/techart/unifiedEL.html">Unified EL</a>.
+ </p>
+ <p>
+ The API and the expression language exploit Java-beans naming patterns through
+ introspection to expose property getters and setters. It also considers public class fields
+ as properties and allows to invoke any accessible method.
+ </p>
+ <p>
+ JEXL attempts to bring some of the lessons learned by the Velocity
+ community about expression languages in templating to a wider audience.
+ <a href="http://commons.apache.org/jelly">Commons Jelly</a> needed
+ Velocity-ish method access, it just had to have it.
+ </p>
+ <p>
+ It must be noted that JEXL is <strong>not</strong> a compatible implementation of EL as defined
+ in JSTL 1.1 (JSR-052) or JSP 2.0 (JSR-152). For a compatible implementation of
+ these specifications, see the <a href="http://commons.apache.org/el">Commons EL</a> project.
+ </p>
+ </section>
+
+ <section name="A Brief Example">
+ <p>
+ When evaluating expressions, JEXL merges an
+ <a href="apidocs/org/apache/commons/jexl2/Expression.html">Expression</a>
+ with a
+ <a href="apidocs/org/apache/commons/jexl2/JexlContext.html">JexlContext</a>.
+ An Expression is created using
+ <a href="apidocs/org/apache/commons/jexl2/JexlEngine.html#createExpression(java.lang.String)">JexlEngine#createExpression()</a>,
+ passing a String containing valid JEXL syntax. A simple JexlContext can be created by instantiating a
+ <a href="apidocs/org/apache/commons/jexl2/MapContext.html">MapContext</a>;
+ a map of variables that will be internally wrapped can be optionally provided through its constructor.
+ The following example, takes a variable named foo, and invokes the bar() method on the property innerFoo:
+ </p>
+
+ <source><![CDATA[
+ // Create or retrieve a JexlEngine
+ JexlEngine jexl = new JexlEngine();
+ // Create an expression object
+ String jexlExp = "foo.innerFoo.bar()";
+ Expression e = jexl.createExpression( jexlExp );
+
+ // Create a context and add data
+ JexlContext jc = new MapContext();
+ jc.set("foo", new Foo() );
+
+ // Now evaluate the expression, getting the result
+ Object o = e.evaluate(jc);
+ ]]>
+ </source>
+ </section>
+
+ <section name="Extensions to JSTL Expression Language">
+ <p>
+ While JEXL is similar to the expression language defined in JSTL, it has improved
+ upon the syntax in a few areas:
+ </p>
+ <ul>
+ <li>Support for invocation of any accessible method (see example above).</li>
+ <li>Support for setting/getting any accessible public field.</li>
+ <li>A general <span class="literal">size()</span> method, which works on:
+ <ul>
+ <li><span class="literal">String</span> - returns length</li>
+ <li><span class="literal">Map</span> - returns number of keys</li>
+ <li><span class="literal">List</span> - returns number of elements.</li>
+ </ul>
+ </li>
+ <li>A general <span class="literal">empty()</span> method, which works on Collections and Strings.</li>
+ <li>A general <span class="literal">new()</span> method allowing to instantiate objects.</li>
+ <li>Support for the ternary operator 'a ? b : c' - and its GNU-C / "Elvis" variant 'a ?: c'.</li>
+ <li>Support for the Perl-like regex matching operators '=~' and '!~'</li>
+ <li>Misc : '+' has been overloaded to be use as a String concatenation operator</li>
+ </ul>
+
+ </section>
+
+ <section name="Releases">
+ <p>
+ The current released version is 2.0.
+ See the <a href="releases.html">releases</a> page for information on obtaining releases.
+ </p>
+ </section>
+
+ <section name="Related Resources">
+ <p>
+ JEXL is not a product of the Java Community Process (JCP), but it provides a
+ similar expression syntax. For more information about JSP 2.0 EL and JSTL 1.1
+ EL:
+ </p>
+ <ul>
+ <li>
+ <a href="http://java.sun.com/products/jsp/index.jsp">JSP 2.0</a> is covered
+ by Java Specification Requests (JSR)
+ <a href="http://www.jcp.org/en/jsr/detail?id=152">JSR-152: JavaServer
+ Pages 2.0 Specification</a>.
+ </li>
+ <li>
+ Apache has an implementation of the expression language for JSP 2.0,
+ called <a href="http://commons.apache.org/el/index.html">EL</a>
+ </li>
+ <li>
+ <a href="http://java.sun.com/products/jsp/jstl/">JSTL 1.1</a> is covered
+ by <a href="http://jcp.org/en/jsr/detail?id=52">JSR 52: A Standard
+ Tag Library for JavaServer Pages</a>. See the
+ <a href="http://java.sun.com/products/jsp/jstl/1.1/docs/api/index.html">JSTL API</a>.
+ </li>
+ <li>Apache has a <a href="http://jakarta.apache.org/taglibs/doc/standard-doc/intro.html">JSTL Implementation</a>.</li>
+ </ul>
+ <subsection name="Velocity">
+ <p>
+ <a href="http://jakarta.apache.org/velocity">Jakarta Velocity</a> implements
+ a similar expression language.
+ </p>
+ <p>
+ In particular the <a href="http://jakarta.apache.org/velocity/user-guide.html#References">References</a>
+ section of the User Guide has some good information on properties and method which correlate
+ directly to JEXL.
+ </p>
+ </subsection>
+ </section>
+
+ <section name="Anyone Using It Yet?">
+ <ul>
+ <li>
+ <a href="http://commons.apache.org/configuration">Commons Configuration</a>
+ </li>
+ <li>
+ <a href="http://commons.apache.org/scxml">Commons SCXML</a>
+ </li>
+ <li>
+ <a href="http://commons.apache.org/jelly">Jelly</a>
+ </li>
+ </ul>
+ </section>
+
+ </body>
+</document>
+
diff --git a/COMMONS_JEXL_2_0/xdocs/issue-tracking.xml b/COMMONS_JEXL_2_0/xdocs/issue-tracking.xml
new file mode 100644
index 0000000..4684cb6
--- /dev/null
+++ b/COMMONS_JEXL_2_0/xdocs/issue-tracking.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You 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.
+-->
+<!--
+ +======================================================================+
+ |**** ****|
+ |**** THIS FILE IS GENERATED BY THE COMMONS BUILD PLUGIN ****|
+ |**** DO NOT EDIT DIRECTLY ****|
+ |**** ****|
+ +======================================================================+
+ | TEMPLATE FILE: issue-tracking-template.xml |
+ | commons-build-plugin/trunk/src/main/resources/commons-xdoc-templates |
+ +======================================================================+
+ | |
+ | 1) Re-generate using: mvn commons:jira-page |
+ | |
+ | 2) Set the following properties in the component's pom: |
+ | - commons.jira.id (required, alphabetic, upper case) |
+ | - commons.jira.pid (required, numeric) |
+ | |
+ | 3) Example Properties |
+ | |
+ | <properties> |
+ | <commons.jira.id>MATH</commons.jira.id> |
+ | <commons.jira.pid>12310485</commons.jira.pid> |
+ | </properties> |
+ | |
+ +======================================================================+
+-->
+<document>
+ <properties>
+ <title>Commons JEXL Issue tracking</title>
+ <author email="dev@commons.apache.org">Commons Documentation Team</author>
+ </properties>
+ <body>
+
+ <section name="Commons JEXL Issue tracking">
+ <p>
+ Commons JEXL uses <a href="http://issues.apache.org/jira/">ASF JIRA</a> for tracking issues.
+ See the <a href="http://issues.apache.org/jira/browse/JEXL">Commons JEXL JIRA project page</a>.
+ </p>
+
+ <p>
+ To use JIRA you may need to <a href="http://issues.apache.org/jira/secure/Signup!default.jspa">create an account</a>
+ (if you have previously created/updated Commons issues using Bugzilla an account will have been automatically
+ created and you can use the <a href="http://issues.apache.org/jira/secure/ForgotPassword!default.jspa">Forgot Password</a>
+ page to get a new password).
+ </p>
+
+ <p>
+ If you would like to report a bug, or raise an enhancement request with
+ Commons JEXL please do the following:
+ <ol>
+ <li><a href="http://issues.apache.org/jira/secure/IssueNavigator.jspa?reset=true&pid=12310479&sorter/field=issuekey&sorter/order=DESC&status=1&status=3&status=4">Search existing open bugs</a>.
+ If you find your issue listed then please add a comment with your details.</li>
+ <li><a href="mail-lists.html">Search the mailing list archive(s)</a>.
+ You may find your issue or idea has already been discussed.</li>
+ <li>Decide if your issue is a bug or an enhancement.</li>
+ <li>Submit either a <a href="http://issues.apache.org/jira/secure/CreateIssueDetails!init.jspa?pid=12310479&issuetype=1&priority=4&assignee=-1">bug report</a>
+ or <a href="http://issues.apache.org/jira/secure/CreateIssueDetails!init.jspa?pid=12310479&issuetype=4&priority=4&assignee=-1">enhancement request</a>.</li>
+ </ol>
+ </p>
+
+ <p>
+ Please also remember these points:
+ <ul>
+ <li>the more information you provide, the better we can help you</li>
+ <li>test cases are vital, particularly for any proposed enhancements</li>
+ <li>the developers of Commons JEXL are all unpaid volunteers</li>
+ </ul>
+ </p>
+
+ <p>
+ For more information on subversion and creating patches see the
+ <a href="http://www.apache.org/dev/contributors.html">Apache Contributors Guide</a>.
+ </p>
+
+ <p>
+ You may also find these links useful:
+ <ul>
+ <li><a href="http://issues.apache.org/jira/secure/IssueNavigator.jspa?reset=true&pid=12310479&sorter/field=issuekey&sorter/order=DESC&status=1&status=3&status=4">All Open Commons JEXL bugs</a></li>
+ <li><a href="http://issues.apache.org/jira/secure/IssueNavigator.jspa?reset=true&pid=12310479&sorter/field=issuekey&sorter/order=DESC&status=5&status=6">All Resolved Commons JEXL bugs</a></li>
+ <li><a href="http://issues.apache.org/jira/secure/IssueNavigator.jspa?reset=true&pid=12310479&sorter/field=issuekey&sorter/order=DESC">All Commons JEXL bugs</a></li>
+ </ul>
+ </p>
+ </section>
+ </body>
+</document>
diff --git a/COMMONS_JEXL_2_0/xdocs/reference/examples.xml b/COMMONS_JEXL_2_0/xdocs/reference/examples.xml
new file mode 100644
index 0000000..3c51b17
--- /dev/null
+++ b/COMMONS_JEXL_2_0/xdocs/reference/examples.xml
@@ -0,0 +1,187 @@
+<?xml version="1.0"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+-->
+
+<document>
+ <properties>
+ <title>Commons JEXL Examples</title>
+ </properties>
+
+ <body>
+ <section name="Overview">
+ <p>
+ In this reference you will find the following topics to help with your use of JEXL.
+ <ul>
+ <li><a href="#Evaluating Expressions">Evaluating Expressions</a></li>
+ <li><a href="#Custom Contexts">Custom Contexts</a></li>
+ <li><a href="#Example Expressions">Example Expressions</a></li>
+ </ul>
+ </p>
+ <p>
+ You can find two sample programs in JEXL's CVS repository:
+ <ul>
+ <li><a href="http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/org/apache/commons/jexl/examples/ArrayTest.java?view=markup">Using arrays</a></li>
+ <li><a href="http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/org/apache/commons/jexl/examples/MethodPropertyTest.java?view=markup">Accessing Properties and invoking methods</a></li>
+ </ul>
+ </p>
+ <p>
+ As well, <a href="http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/org/apache/commons/jexl/JexlTest.java?view=markup">JEXL's Unit Tests</a>
+ provide handy examples of expressions. The test code also contains a
+ <a href="http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/org/apache/commons/jexl/Jexl.java?view=markup">simple class</a>
+ that evaluates its command line arguments as JEXL expressions when run.
+ </p>
+ </section>
+ <section name="Evaluating Expressions">
+ <p>
+ To evaluate expressions using JEXL, you need two things:
+ <ul>
+ <li>A <a href="http://commons.apache.org/jexl/apidocs/org/apache/commons/jexl/JexlContext.html">context</a> containing any variables, and</li>
+ <li>An <a href="http://commons.apache.org/jexl/apidocs/org/apache/commons/jexl/Expression.html">expression</a></li>
+ </ul>
+ </p>
+ <p>
+ The easiest way of obtaining a a context is to use the
+ <a href="http://commons.apache.org/jexl/apidocs/org/apache/commons/jexl/JexlHelper.html#createContext()">new JexlContext.Mapped()</a>
+ method. This creates a context which is simply an extension of a
+ <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/HashMap.html">HashMap</a>
+ </p>
+ <p>
+ <a href="http://commons.apache.org/jexl/apidocs/org/apache/commons/jexl/Expression.html">Expressions</a> are
+ created using the <a href="http://commons.apache.org/jexl/apidocs/org/apache/commons/jexl/ExpressionFactory.html#createExpression(java.lang.String)">ExpressionFactory.createExpression(String)</a>
+ method.
+ </p>
+ <p>
+ Once you have your expression, you can then use use the
+ <a href="http://commons.apache.org/jexl/apidocs/org/apache/commons/jexl/Expression.html#evaluate(org.apache.commons.jexl2.JexlContext)">evaluate</a>
+ to execute it and obtain a result.
+ </p>
+ <p>
+ Here's a typical scenario:
+ </p>
+ <source>
+ // Create an expression object for our calculation
+ String calculateTax = taxManager.getTaxCalc(); //e.g. "((G1 + G2 + G3) * 0.1) + G4";
+ Expression e = ExpressionFactory.createExpression( calculateTax );
+
+ // populate the context
+ JexlContext context = new JexlContext.Mapped();
+ context.set("G1", businessObject.getTotalSales());
+ context.set("G2", taxManager.getTaxCredit(businessObject.getYear()));
+ context.set("G3", businessObject.getIntercompanyPayments());
+ context.set("G4", -taxManager.getAllowances());
+ // ...
+
+ // work it out
+ Float result = (Float)e.evaluate(context);
+ </source>
+ </section>
+ <section name="Custom Contexts">
+ <p>
+ Often you have the objects and values that are needed in the context available
+ elsewhere, and instead of creating the default context and populating it
+ manually in the code, it may be simpler to create a context implementation of your
+ own.
+ </p>
+ <p>
+ The <a href="http://commons.apache.org/jexl/apidocs/org/apache/commons/jexl/JexlContext.html">JexlContext</a>
+ interface is very simple with only two methods, one to get the variables of the
+ context as a <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Map.html">Map</a> and
+ another to set the variables of the context from a Map.
+ </p>
+ <p>
+ Here's a simple context that wraps the JVM's system properties:
+ <source>
+ JexlContext context = new JexlContext() {
+ public Map getVars() { return System.getProperties(); }
+ public void setVars(Map map) { }
+ };
+ </source>
+ </p>
+ </section>
+ <section name="Example Expressions">
+ <!-- invoking methods, property access, array access, empty, size etc... -->
+ <subsection name="Arithmetic">
+ <p>Most valid arithmetic expressions in Java are also valid in Jexl.</p>
+ <source>
+1 + 2
+12.0 - 5.2
+6 * 12 + 5 / 2.6
+12 % 2
+6 / 4
+-12 + 77.2
+x * 1.1 + y
+ </source>
+ <p>Arithmetic expressions can use variables. <code>null</code> can be treated as a zero for arithmetic.</p>
+ </subsection>
+ <subsection name="Calling methods">
+ <p>
+ JEXL allows you to call any method on a Java object using the same syntax.
+ If you have a string in the context under the name <code>aString</code>,
+ you could call it's <code>length</code>
+ method like this:
+ <source>
+aString.length()
+aString.substring(6)
+ </source>
+ </p>
+ <p>
+ Often the values you want to pass to a method are other variables or expressions.
+ If you have a number in the context, named <code>i</code>, you could use it
+ in a method call:
+ <source>aString.substring(i)</source>
+ </p>
+ </subsection>
+ <subsection name="Accessing properties">
+ <p>
+ JEXL provides a shorthand syntax to access methods that
+ follow the JavaBean naming convention for properties, i.e.
+ setters and getters.
+ </p>
+ <p>
+ If you have some object foo in the context and it has a
+ method <code>getBar()</code>, you can call that method using
+ the following syntax:
+ <source>foo.bar</source>
+ </p>
+ <p>
+ Since <code>java.lang.Object</code> has a <code>getClass()</code> method
+ that returns a <code>java.lang.Class</code> object, and the
+ class has a <code>getName()</code> method, the following is a shorthand
+ for obtaining the class name of an object <code>foo</code> in the context:
+ <source>foo.class.name</source>
+ </p>
+ </subsection>
+ <subsection name="Arrays, Lists and Maps">
+ <p>
+ Array elements can be accessed using either square brackets or a dotted
+ index notation, e.g. the following are equivalent
+ <source>arr[0]
+arr.0</source>
+ The same holds true for lists.
+ </p>
+ <p>
+ For a map, the syntax is very similar, except the 'index' is an object, e.g.
+ the following are equivalent.
+ <source>aMap['key']
+aMap.get('key')</source>
+ Please note that the index does not have to be a string, and
+ that the string usage above is just one possible option.
+ </p>
+ </subsection>
+ </section>
+ </body>
+</document>
diff --git a/COMMONS_JEXL_2_0/xdocs/reference/index.xml b/COMMONS_JEXL_2_0/xdocs/reference/index.xml
new file mode 100644
index 0000000..517f82f
--- /dev/null
+++ b/COMMONS_JEXL_2_0/xdocs/reference/index.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+-->
+
+<document>
+ <properties>
+ <title>Commons JEXL Reference</title>
+ </properties>
+
+ <body>
+ <section name="Reference">
+ <p>
+ The JEXL Reference documentation is made up of the following:
+ <ul>
+ <li><a href="syntax.html">JEXL Syntax</a></li>
+ <li><a href="examples.html">Common examples</a> using JEXL and the expression language</li>
+ <li><a href="jsr223.html">JSR-223 (scripting)</a> using JEXL via JSR-223 (scripting)</li>
+ </ul>
+ </p>
+ </section>
+ </body>
+</document>
+
diff --git a/COMMONS_JEXL_2_0/xdocs/reference/jsr223.xml b/COMMONS_JEXL_2_0/xdocs/reference/jsr223.xml
new file mode 100644
index 0000000..cb80cbb
--- /dev/null
+++ b/COMMONS_JEXL_2_0/xdocs/reference/jsr223.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+-->
+
+<document>
+ <properties>
+ <title>Commons JEXL JSR-223 (scripting) Reference</title>
+ </properties>
+
+ <body>
+ <section name="Overview">
+ <p>
+ Commons JEXL includes JSR-223 (javax.script) support.
+ The binary jar includes the scripting factory
+ and the services definition javax.script.ScriptEngineFactory,
+ so no special configuration is needed.
+ </p>
+ </section>
+ <section name="Script engine support">
+ <p>
+ The provided script engine implements the following:
+ <ul>
+ <li>Language names: "JEXL", "Jexl", "jexl"</li>
+ <li>Extensions: ".jexl"</li>
+ <li>Mime-types: "application/x-jexl"</li>
+ </ul>
+ The implementation adds an instance of
+ <a href="http://commons.apache.org/jexl/apidocs/org/apache/commons/jexl/scripting/JexlScriptObject.html">JexlScriptObject</a>
+ to the engine context as the variable "JEXL".
+ This gives scripts easier access to various items such as System.out and a logger.
+ </p>
+ </section>
+ <section name="Using the JSR-223 JEXL test application">
+ <p>
+ The binary release includes a command-line application which can be used to exercise the JSR-223 script engine.
+ For example:
+ <source>java -cp commons-jexl-2.0.jar;commons-logging-1.1.1.jar[;bsf-api-3.0.jar] org.apache.commons.jexl2.scripting.Main script.jexl</source>
+ If a single argument is provided, then that is assumed to be the name of a script file;
+ otherwise, the application prompts for script input to be evaluated.
+ In both cases, the variable "args" contains the command-line arguments.
+ [Note that Java 1.5 does not include javax.script support; you will need to use the Apache BSF API jar as indicated.]
+ </p>
+ </section>
+ <section name="Using JEXL with JSR-223 on Java 1.5">
+ <p>
+ In order to use JEXL via JSR-223 on Java 1.5, you need to add Apache BSF-API 3.0 jar to the classpath.
+ JEXL also requires Commons Logging on the classpath.
+ </p>
+ </section>
+ <section name="Using JEXL with JSR-223 on Java 1.6+">
+ <p>
+ JSR-223 support is included with Java 1.6+.
+ JEXL requires Commons Logging, which needs to be included in the path.
+ </p>
+ </section>
+ <section name="JSR-223 support classes">
+ <p>
+ The classes used to support JSR-223 scripting access are:
+ <ul>
+ <li>org.apache.commons.jexl2.scripting.JexlScriptEngineFactory - the factory</li>
+ <li>org.apache.commons.jexl2.scripting.JexlScriptEngine - the engine</li>
+ <li>org.apache.commons.jexl2.scripting.JexlScriptObject - class used to give scripts access to JEXL objects</li>
+ </ul>
+ </p>
+ </section>
+ </body>
+</document>
+
diff --git a/COMMONS_JEXL_2_0/xdocs/reference/syntax.xml b/COMMONS_JEXL_2_0/xdocs/reference/syntax.xml
new file mode 100644
index 0000000..8d8339b
--- /dev/null
+++ b/COMMONS_JEXL_2_0/xdocs/reference/syntax.xml
@@ -0,0 +1,505 @@
+<?xml version="1.0"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+-->
+
+<document>
+ <properties>
+ <title>Commons JEXL Syntax</title>
+ </properties>
+
+ <body>
+ <section name="Overview">
+ <p>
+ This reference is split up into the following sections:
+ <ol>
+ <li><a href="#Language Elements">Language Elements</a></li>
+ <li><a href="#Literals">Literals</a></li>
+ <li><a href="#Functions">Functions</a></li>
+ <li><a href="#Operators">Operators</a></li>
+ <li><a href="#Conditional">Conditional Statements</a></li>
+ </ol>
+ </p>
+ <p>
+ For more technical information about the JEXL Grammar, you can find the
+ <a href="https://javacc.dev.java.net/">JavaCC</a> grammar for JEXL
+ here: <a href="http://svn.apache.org/viewcvs.cgi/jakarta/commons/proper/jexl/trunk/src/java/org/apache/commons/jexl/parser/Parser.jj?view=markup">Parser.jj</a>
+ </p>
+ </section>
+ <section name="Language Elements">
+ <table>
+ <tr><th width="15%">Item</th><th>Description</th></tr>
+ <tr>
+ <td>Comments</td>
+ <td>
+ Specified using <code>##</code> or <code>//</code>and extend to the end of line, e.g.
+ <source>## This is a comment</source>
+ Also specified using <code>//</code>, e.g.
+ <source>// This is a comment</source>
+ Multiple lines comments are specified using <code>/*...*/</code>, e.g.
+ <source>/* This is a
+ multi-line comment */</source>
+ </td>
+ </tr>
+ <tr>
+ <td>Identifiers / variables</td>
+ <td>
+ Must start with <code>a-z</code>, <code>A-Z</code>, <code>_</code> or <code>$</code>.
+ Can then be followed by <code>0-9</code>, <code>a-z</code>, <code>A-Z</code>, <code>_</code> or <code>$</code>.
+ e.g.
+ <ul>
+ <li>Valid: <code>var1</code>,<code>_a99</code>,<code>$1</code></li>
+ <li>Invalid: <code>9v</code>,<code>!a99</code>,<code>1$</code></li>
+ </ul>
+ <p>
+ Variable names are <strong>case-sensitive</strong>, e.g. <code>var1</code> and <code>Var1</code> are different variables.
+ </p>
+ <p>
+ <strong>NOTE:</strong> JEXL does not support variables with hyphens in them, e.g.
+ <source>commons-logging // invalid variable name (hyphenated)</source> is not a valid variable, but instead is treated as a
+ subtraction of the variable <code>logging</code> from the variable <code>commons</code>
+ </p>
+ <p>
+ JEXL also supports <code>ant-style</code> variables, the following is a valid variable name:
+ <source>my.dotted.var</source>
+ </p>
+ <p>
+ <strong>N.B.</strong> the following keywords are reserved, and cannot be used as a variable name or property when using the dot operator:
+ <code>or and eq ne lt gt le ge div mod not null true false new</code>
+ For example, the following is invalid:
+ <source>my.new.dotted.var // invalid ('new' is keyword)</source>
+ In such cases, the [ ] operator can be used, for example:
+ <source>my['new'].dotted.var</source>
+ </p>
+ </td>
+ </tr>
+ <tr>
+ <td>Scripts</td>
+ <td>
+ A script in Jexl is made up of zero or more statements. Scripts can be read from a String, File or URL.
+ </td>
+ </tr>
+ <tr>
+ <td>Statements</td>
+ <td>
+ A statement can be the empty statement, the semicolon (<code>;</code>) , block, assignment or an expression.
+ Statements are optionally terminated with a semicolon.
+ </td>
+ </tr>
+ <tr>
+ <td>Block</td>
+ <td>
+ A block is simply multiple statements inside curly braces (<code>{, }</code>).
+ </td>
+ </tr>
+ <tr>
+ <td>Assignment</td>
+ <td>
+ Assigns the value of a variable (<code>my.var = 'a value'</code>) using a
+ <code>JexlContext</code> as initial resolver. Both <em>beans</em> and <em>ant-ish</em>
+ variables assignment are supported.
+ </td>
+ </tr>
+ <tr>
+ <td>Method calls</td>
+ <td>
+ Calls a method of an object, e.g.
+ <source>"hello world".hashCode()</source> will call the <code>hashCode</code> method
+ of the <code>"hello world"</code> String.
+ <p>In case of multiple arguments and overloading, Jexl will make the best effort to find
+ the most appropriate non ambiguous method to call.</p>
+ </td>
+ </tr>
+ </table>
+ </section>
+ <section name="Literals">
+ <table>
+ <tr><th width="15%">Item</th><th>Description</th></tr>
+ <tr>
+ <td>Integer Literals</td>
+ <td>1 or more digits from <code>0</code> to <code>9</code></td>
+ </tr>
+ <tr>
+ <td>Floating point Literals</td>
+ <td>
+ 1 or more digits from <code>0</code> to <code>9</code>, followed
+ by a decimal point and then one or more digits from
+ <code>0</code> to <code>9</code>.
+ </td>
+ </tr>
+ <tr>
+ <td>String literals</td>
+ <td>
+ Can start and end with either <code>'</code> or <code>"</code> delimiters, e.g.
+ <source>"Hello world"</source> and
+ <source>'Hello world'</source> are equivalent.
+ <p>The escape character is <code>\</code>; it only escapes the string delimiter</p>
+ </td>
+ </tr>
+ <tr>
+ <td>Boolean literals</td>
+ <td>
+ The literals <code>true</code> and <code>false</code> can be used, e.g.
+ <source>val1 == true</source>
+ </td>
+ </tr>
+ <tr>
+ <td>Null literal</td>
+ <td>
+ The null value is represented as in java using the literal <code>null</code>, e.g.
+ <source>val1 == null</source>
+ </td>
+ </tr>
+ <tr>
+ <td>Array literal</td>
+ <td>
+ A <code>[</code> followed by one or more expressions separated by <code>,</code> and ending
+ with <code>]</code>, e.g.
+ <source>[ 1, 2, "three" ]</source>
+ <p>This syntax creates an <code>Object[]</code>.</p>
+ <p>
+ JEXL will attempt to strongly type the array; if all entries are of the same class or if all
+ entries are Number instance, the array literal will be an <code>MyClass[]</code> in the former
+ case, a <code>Number[]</code> in the latter case.</p>
+ <p>Furthermore, if all entries in the array literal are of the same class
+ and that class has an equivalent primitive type, the array returned will be a primitive array. e.g.
+ <code>[1, 2, 3]</code> will be interpreted as <code>int[]</code>.</p>
+ </td>
+ </tr>
+ <tr>
+ <td>Map literal</td>
+ <td>
+ A <code>{</code> followed by one or more sets of <code>key : value</code> pairs separated by <code>,</code> and ending
+ with <code>}</code>, e.g.
+ <source>{ "one" : 1, "two" : 2, "three" : 3, "more": "many more" }</source>
+ <p>This syntax creates a <code>HashMap<Object,Object></code>.</p>
+ </td>
+ </tr>
+ </table>
+ </section>
+ <section name="Functions">
+ <table>
+ <tr><th width="15%">Function</th><th>Description</th></tr>
+ <tr>
+ <td>empty</td>
+ <td>
+ Returns true if the expression following is either:
+ <ol>
+ <li><code>null</code></li>
+ <li>An empty string</li>
+ <li>An array of length zero</li>
+ <li>A collection of size zero</li>
+ <li>An empty map</li>
+ </ol>
+ <source>empty(var1)</source>
+ </td>
+ </tr>
+ <tr>
+ <td>size</td>
+ <td>
+ Returns the information about the expression:
+ <ol>
+ <li>Length of an array</li>
+ <li>Size of a List</li>
+ <li>Size of a Map</li>
+ <li>Size of a Set</li>
+ <li>Length of a string</li>
+ </ol>
+ <source>size("Hello")</source> returns 5.
+ </td>
+ </tr>
+ <tr>
+ <td>new</td>
+ <td>
+ Creates a new instance using a fully-qualified class name or Class:
+ <source>new("java.lang.Double", 10)</source> returns 10.0.
+ <p>Note that the first argument of <code>new</code> can be a variable or any
+ expression evaluating as a String or Class; the rest of the arguments are used
+ as arguments to the constructor for the class considered.</p>
+ <p>In case of multiple constructors, Jexl will make the best effort to find
+ the most appropriate non ambiguous constructor to call.</p>
+ </td>
+ </tr>
+ <tr>
+ <td>ns:function</td>
+ <td>
+ A <code>JexlEngine</code> can register objects or classes used as function namespaces.
+ This can allow expressions like:
+ <source>math:cosinus(23.0)</source>
+ </td>
+ </tr>
+ </table>
+ </section>
+ <section name="Operators">
+ <table>
+ <tr><th width="15%">Operator</th><th>Description</th></tr>
+ <tr>
+ <td>Boolean <code>and</code></td>
+ <td>
+ The usual <code>&&</code> operator can be used as well as the word <code>and</code>, e.g.
+ <source>cond1 and cond2</source> and
+ <source>cond1 && cond2</source> are equivalent
+ </td>
+ </tr>
+ <tr>
+ <td>Boolean <code>or</code></td>
+ <td>
+ The usual <code>||</code> operator can be used as well as the word <code>or</code>, e.g.
+ <source>cond1 or cond2</source> and
+ <source>cond1 || cond2</source> are equivalent
+ </td>
+ </tr>
+ <tr>
+ <td>Boolean <code>not</code></td>
+ <td>
+ The usual <code>!</code> operator can be used as well as the word <code>not</code>, e.g.
+ <source>!cond1</source> and
+ <source>not cond1</source> are equivalent
+ </td>
+ </tr>
+ <tr>
+ <td>Bitwise <code>and</code></td>
+ <td>
+ The usual <code>&</code> operator is used, e.g.
+ <source>33 & 4</source>, 0010 0001 & 0000 0100 = 0.
+ </td>
+ </tr>
+ <tr>
+ <td>Bitwise <code>or</code></td>
+ <td>
+ The usual <code>|</code> operator is used, e.g.
+ <source>33 | 4</source>, 0010 0001 | 0000 0100 = 0010 0101 = 37.
+ </td>
+ </tr>
+ <tr>
+ <td>Bitwise <code>xor</code></td>
+ <td>
+ The usual <code>^</code> operator is used, e.g.
+ <source>33 ^ 4</source>, 0010 0001 ^ 0000 0100 = 0010 0100 = 37.
+ </td>
+ </tr>
+ <tr>
+ <td>Bitwise <code>complement</code></td>
+ <td>
+ The usual <code>~</code> operator is used, e.g.
+ <source>~33</source>, ~0010 0001 = 1101 1110 = -34.
+ </td>
+ </tr>
+ <tr>
+ <td>Ternary conditional <code>?:</code> </td>
+ <td>
+ The usual ternary conditional operator <code>condition ? if_true : if_false</code> operator can be
+ used as well as the abbreviation <code>value ?: if_false</code> which returns the <code>value</code> if
+ its evaluation is defined, non-null and non-false, e.g.
+ <source>val1 ? val1 : val2</source> and
+ <source>val1 ?: val2 </source> are equivalent.
+ <p>
+ <strong>NOTE:</strong> The condition will evaluate to <code>false</code> when it
+ refers to an undefined variable or <code>null</code> for all <code>JexlEngine</code>
+ flag combinations. This allows explicit syntactic leniency and treats the condition
+ 'if undefined or null or false' the same way in all cases.
+ </p>
+ </td>
+ </tr>
+ <tr>
+ <td>Equality</td>
+ <td>
+ The usual <code>==</code> operator can be used as well as the abbreviation <code>eq</code>.
+ For example
+ <source>val1 == val2</source> and
+ <source>val1 eq val2</source> are equivalent.
+ <ol>
+ <li>
+ <code>null</code> is only ever equal to null, that is if you compare null
+ to any non-null value, the result is false.
+ </li>
+ <li>Equality uses the java <code>equals</code> method</li>
+ </ol>
+ </td>
+ </tr>
+ <tr>
+ <td>Inequality</td>
+ <td>
+ The usual <code>!=</code> operator can be used as well as the abbreviation <code>ne</code>.
+ For example
+ <source>val1 != val2</source> and
+ <source>val1 ne val2</source> are equivalent.
+ </td>
+ </tr>
+ <tr>
+ <td>Less Than</td>
+ <td>
+ The usual <code><</code> operator can be used as well as the abbreviation <code>lt</code>.
+ For example
+ <source>val1 < val2</source> and
+ <source>val1 lt val2</source> are equivalent.
+ </td>
+ </tr>
+ <tr>
+ <td>Less Than Or Equal To</td>
+ <td>
+ The usual <code><=</code> operator can be used as well as the abbreviation <code>le</code>.
+ For example
+ <source>val1 <= val2</source> and
+ <source>val1 le val2</source> are equivalent.
+ </td>
+ </tr>
+ <tr>
+ <td>Greater Than</td>
+ <td>
+ The usual <code>></code> operator can be used as well as the abbreviation <code>gt</code>.
+ For example
+ <source>val1 > val2</source> and
+ <source>val1 gt val2</source> are equivalent.
+ </td>
+ </tr>
+ <tr>
+ <td>Greater Than Or Equal To</td>
+ <td>
+ The usual <code>>=</code> operator can be used as well as the abbreviation <code>ge</code>.
+ For example
+ <source>val1 >= val2</source> and
+ <source>val1 ge val2</source> are equivalent.
+ </td>
+ </tr>
+ <tr>
+ <td>Regex match <code>=~</code></td>
+ <td>
+ The Perl inspired <code>=~</code> operator can be used to check that a <code>string</code> matches
+ a regular expression (expressed either a Java String or a java.util.regex.Pattern).
+ For example
+ <code>"abcdef" =~ "abc.*</code> returns <code>true</code>.
+ </td>
+ </tr>
+ <tr>
+ <td>Regex no-match <code>!~</code></td>
+ <td>
+ The Perl inspired <code>!~</code> operator can be used to check that a <code>string</code> does not
+ match a regular expression (expressed either a Java String or a java.util.regex.Pattern).
+ For example
+ <code>"abcdef" !~ "abc.*</code> returns <code>false</code>.
+ </td>
+ </tr>
+ <tr>
+ <td>Addition</td>
+ <td>
+ The usual <code>+</code> operator is used.
+ For example
+ <source>val1 + val2</source>
+ </td>
+ </tr>
+ <tr>
+ <td>Subtraction</td>
+ <td>
+ The usual <code>-</code> operator is used.
+ For example
+ <source>val1 - val2</source>
+ </td>
+ </tr>
+ <tr>
+ <td>Multiplication</td>
+ <td>
+ The usual <code>*</code> operator is used.
+ For example
+ <source>val1 * val2</source>
+ </td>
+ </tr>
+ <tr>
+ <td>Division</td>
+ <td>
+ The usual <code>/</code> operator is used, or one can use the <code>div</code> operator.
+ For example
+ <source>val1 / val2</source>
+ or
+ <source>val1 div val2</source>
+ </td>
+ </tr>
+ <tr>
+ <td>Modulus (or remainder)</td>
+ <td>
+ The <code>%</code> operator is used. An alternative is the <code>mod</code>
+ operator.
+ For example
+ <source>5 mod 2</source> gives 1 and is equivalent to <source>5 % 2</source>
+ </td>
+ </tr>
+ <tr>
+ <td>Negation</td>
+ <td>
+ The unary <code>-</code> operator is used.
+ For example
+ <source>-12</source>
+ </td>
+ </tr>
+ <tr>
+ <td>Array access</td>
+ <td>
+ Array elements may be accessed using either square brackets or a dotted numeral, e.g.
+ <source>arr1[0]</source> and <source>arr1.0</source> are equivalent
+ </td>
+ </tr>
+ <tr>
+ <td>HashMap access</td>
+ <td>
+ Map elements are accessed using square brackets, e.g.
+ <source>map[0]; map['name']; map[var];</source>
+ Note that <source>map['7']</source> and <source>map[7]</source> refer to different elements.
+ Map elements with a numeric key may also be accessed using a dotted numeral, e.g.
+ <source>map[0]</source> and <source>map.0</source> are equivalent.
+ </td>
+ </tr>
+ </table>
+ </section>
+ <section name="Conditional">
+ <table>
+ <tr><th width="15%">Operator</th><th>Description</th></tr>
+ <tr>
+ <td>if</td>
+ <td>
+ Classic, if/else statement, e.g.
+ <source>if ((x * 2) == 5) {
+ y = 1;
+} else {
+ y = 2;
+}</source>
+ </td>
+ </tr>
+ <tr>
+ <td>for</td>
+ <td>
+ Loop through items of an Array, Collection, Map, Iterator or Enumeration, e.g.
+ <source>for(item : list) {
+ x = x + item;
+}</source>
+ Where <code>item</code> and <code>list</code> are variables.
+ The JEXL 1.1 syntax using <code>foreach(item in list)</code> is now <strong>deprecated</strong>.
+ </td>
+ </tr>
+ <tr>
+ <td>while</td>
+ <td>
+ Loop until a condition is satisfied, e.g.
+ <source>while (x lt 10) {
+ x = x + 2;
+}</source>
+ </td>
+ </tr>
+ </table>
+ </section>
+
+ </body>
+</document>
+
diff --git a/COMMONS_JEXL_2_0/xdocs/releases.xml b/COMMONS_JEXL_2_0/xdocs/releases.xml
new file mode 100644
index 0000000..39216a4
--- /dev/null
+++ b/COMMONS_JEXL_2_0/xdocs/releases.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+-->
+<document>
+ <properties>
+ <title>Downloads</title>
+ </properties>
+
+ <body>
+ <section name="Releases">
+ <p><strong>Latest Stable Release</strong></p>
+ <ul>
+ <li>
+ <a href="http://commons.apache.org/downloads/download_jexl.cgi">2.0 Binary/Source</a>
+ </li>
+ </ul>
+ <p><strong>Nightly Builds</strong></p>
+ <ul>
+ <li>
+ <a href="http://people.apache.org/builds/commons/nightly/commons-jexl/">Binary/Source</a>
+ </li>
+ </ul>
+ <p>
+ <strong>Archived Releases</strong>
+ <br/>
+ Older releases are retained by the Apache Software Foundation but are
+ moved into a
+ <a href="http://archive.apache.org/dist/commons/jexl/">
+ special archive area
+ </a>.
+ </p>
+ </section>
+ </body>
+</document>