[SCM] libbeansbinding-java packaging branch, upstream, updated. upstream/1.2.1-1-g20d471a

Andres Mejia ceros-guest at alioth.debian.org
Sat Jan 22 02:57:50 UTC 2011


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "libbeansbinding-java packaging".

The branch, upstream has been updated
       via  20d471a0344e18c3f0f0a1f2be0f031c55fece9f (commit)
      from  273f92e78918a02b1957a98e70292edd36668664 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
-----------------------------------------------------------------------

Summary of changes:
 beansbinding-1.2.1-src.zip                         |  Bin 236905 -> 0 bytes
 build.xml                                          |   95 ++
 license.txt                                        |  511 ++++++
 nbproject/build-impl.xml                           |  541 +++++++
 nbproject/genfiles.properties                      |    8 +
 nbproject/project.properties                       |   56 +
 nbproject/project.xml                              |   17 +
 releaseNotes.txt                                   |  252 +++
 ...g.jdesktop.beansbinding.ext.BeanAdapterProvider |   10 +
 .../beansbinding/AbstractBindingListener.java      |   89 +
 src/org/jdesktop/beansbinding/AutoBinding.java     |  254 +++
 src/org/jdesktop/beansbinding/BeanProperty.java    | 1020 ++++++++++++
 src/org/jdesktop/beansbinding/Binding.java         | 1625 +++++++++++++++++++
 src/org/jdesktop/beansbinding/BindingGroup.java    |  292 ++++
 src/org/jdesktop/beansbinding/BindingListener.java |   67 +
 src/org/jdesktop/beansbinding/Bindings.java        |   82 +
 src/org/jdesktop/beansbinding/Converter.java       |  230 +++
 src/org/jdesktop/beansbinding/ELProperty.java      |  923 +++++++++++
 src/org/jdesktop/beansbinding/ObjectProperty.java  |  132 ++
 src/org/jdesktop/beansbinding/Property.java        |  119 ++
 src/org/jdesktop/beansbinding/PropertyHelper.java  |  229 +++
 src/org/jdesktop/beansbinding/PropertyPath.java    |  150 ++
 .../beansbinding/PropertyResolutionException.java  |   43 +
 .../jdesktop/beansbinding/PropertyStateEvent.java  |  252 +++
 .../beansbinding/PropertyStateListener.java        |   26 +
 src/org/jdesktop/beansbinding/TempELContext.java   |  106 ++
 src/org/jdesktop/beansbinding/Validator.java       |   86 +
 .../beansbinding/ext/BeanAdapterFactory.java       |  222 +++
 .../beansbinding/ext/BeanAdapterProvider.java      |   18 +
 src/org/jdesktop/beansbinding/ext/package.html     |   53 +
 src/org/jdesktop/beansbinding/package.html         |   97 ++
 src/org/jdesktop/el/ArrayELResolver.java           |  330 ++++
 src/org/jdesktop/el/BeanELResolver.java            |  542 +++++++
 src/org/jdesktop/el/CompositeELResolver.java       |  475 ++++++
 src/org/jdesktop/el/ELContext.java                 |  211 +++
 src/org/jdesktop/el/ELContextEvent.java            |   37 +
 src/org/jdesktop/el/ELContextListener.java         |   25 +
 src/org/jdesktop/el/ELException.java               |   56 +
 src/org/jdesktop/el/ELResolver.java                |  324 ++++
 src/org/jdesktop/el/ELUtil.java                    |  156 ++
 src/org/jdesktop/el/Expression.java                |  186 +++
 src/org/jdesktop/el/ExpressionFactory.java         |  212 +++
 src/org/jdesktop/el/FunctionMapper.java            |   36 +
 src/org/jdesktop/el/ListELResolver.java            |  362 +++++
 src/org/jdesktop/el/MapELResolver.java             |  357 +++++
 src/org/jdesktop/el/MethodExpression.java          |  105 ++
 src/org/jdesktop/el/MethodInfo.java                |   60 +
 src/org/jdesktop/el/MethodNotFoundException.java   |   54 +
 src/org/jdesktop/el/PrivateMessages.properties     |   12 +
 src/org/jdesktop/el/PropertyNotFoundException.java |   60 +
 .../jdesktop/el/PropertyNotWritableException.java  |   63 +
 src/org/jdesktop/el/ResourceBundleELResolver.java  |  275 ++++
 src/org/jdesktop/el/ValueExpression.java           |  183 +++
 src/org/jdesktop/el/VariableMapper.java            |   40 +
 .../jdesktop/el/impl/ExpressionFactoryImpl.java    |   66 +
 src/org/jdesktop/el/impl/Messages.properties       |   51 +
 src/org/jdesktop/el/impl/MethodExpressionImpl.java |  302 ++++
 .../jdesktop/el/impl/MethodExpressionLiteral.java  |   83 +
 src/org/jdesktop/el/impl/ValueExpressionImpl.java  |  273 ++++
 .../jdesktop/el/impl/ValueExpressionLiteral.java   |  100 ++
 src/org/jdesktop/el/impl/lang/ELArithmetic.java    |  364 +++++
 src/org/jdesktop/el/impl/lang/ELSupport.java       |  463 ++++++
 .../jdesktop/el/impl/lang/EvaluationContext.java   |  126 ++
 .../jdesktop/el/impl/lang/ExpressionBuilder.java   |  202 +++
 .../el/impl/lang/FunctionMapperFactory.java        |   47 +
 .../jdesktop/el/impl/lang/FunctionMapperImpl.java  |  189 +++
 .../el/impl/lang/VariableMapperFactory.java        |   41 +
 .../jdesktop/el/impl/lang/VariableMapperImpl.java  |   44 +
 src/org/jdesktop/el/impl/lang/package.html         |   19 +
 src/org/jdesktop/el/impl/package.html              |   19 +
 .../jdesktop/el/impl/parser/ArithmeticNode.java    |   29 +
 src/org/jdesktop/el/impl/parser/AstAnd.java        |   40 +
 .../jdesktop/el/impl/parser/AstBracketSuffix.java  |   26 +
 src/org/jdesktop/el/impl/parser/AstChoice.java     |   38 +
 .../el/impl/parser/AstCompositeExpression.java     |   46 +
 .../el/impl/parser/AstDeferredExpression.java      |   41 +
 src/org/jdesktop/el/impl/parser/AstDiv.java        |   36 +
 src/org/jdesktop/el/impl/parser/AstDotSuffix.java  |   26 +
 .../el/impl/parser/AstDynamicExpression.java       |   41 +
 src/org/jdesktop/el/impl/parser/AstEmpty.java      |   49 +
 src/org/jdesktop/el/impl/parser/AstEqual.java      |   35 +
 src/org/jdesktop/el/impl/parser/AstFalse.java      |   26 +
 .../jdesktop/el/impl/parser/AstFloatingPoint.java  |   46 +
 src/org/jdesktop/el/impl/parser/AstFunction.java   |  125 ++
 .../jdesktop/el/impl/parser/AstGreaterThan.java    |   39 +
 .../el/impl/parser/AstGreaterThanEqual.java        |   41 +
 src/org/jdesktop/el/impl/parser/AstIdentifier.java |  156 ++
 src/org/jdesktop/el/impl/parser/AstInteger.java    |   46 +
 src/org/jdesktop/el/impl/parser/AstLessThan.java   |   39 +
 .../jdesktop/el/impl/parser/AstLessThanEqual.java  |   41 +
 .../el/impl/parser/AstLiteralExpression.java       |   51 +
 src/org/jdesktop/el/impl/parser/AstMinus.java      |   36 +
 src/org/jdesktop/el/impl/parser/AstMod.java        |   36 +
 src/org/jdesktop/el/impl/parser/AstMult.java       |   36 +
 src/org/jdesktop/el/impl/parser/AstNegative.java   |   75 +
 src/org/jdesktop/el/impl/parser/AstNot.java        |   37 +
 src/org/jdesktop/el/impl/parser/AstNotEqual.java   |   35 +
 src/org/jdesktop/el/impl/parser/AstNull.java       |   31 +
 src/org/jdesktop/el/impl/parser/AstOr.java         |   40 +
 src/org/jdesktop/el/impl/parser/AstPlus.java       |   36 +
 src/org/jdesktop/el/impl/parser/AstString.java     |   62 +
 src/org/jdesktop/el/impl/parser/AstTrue.java       |   26 +
 src/org/jdesktop/el/impl/parser/AstValue.java      |  158 ++
 src/org/jdesktop/el/impl/parser/BooleanNode.java   |   27 +
 src/org/jdesktop/el/impl/parser/ELParser.java      | 1676 ++++++++++++++++++++
 .../jdesktop/el/impl/parser/ELParserConstants.java |  130 ++
 .../el/impl/parser/ELParserTokenManager.java       | 1285 +++++++++++++++
 .../el/impl/parser/ELParserTreeConstants.java      |   81 +
 .../jdesktop/el/impl/parser/JJTELParserState.java  |  127 ++
 src/org/jdesktop/el/impl/parser/Node.java          |   57 +
 src/org/jdesktop/el/impl/parser/NodeVisitor.java   |   16 +
 .../jdesktop/el/impl/parser/ParseException.java    |  196 +++
 .../jdesktop/el/impl/parser/SimpleCharStream.java  |  405 +++++
 src/org/jdesktop/el/impl/parser/SimpleNode.java    |  147 ++
 src/org/jdesktop/el/impl/parser/Token.java         |   85 +
 src/org/jdesktop/el/impl/parser/TokenMgrError.java |  137 ++
 src/org/jdesktop/el/impl/parser/package.html       |   19 +
 src/org/jdesktop/el/impl/util/MessageFactory.java  |   59 +
 src/org/jdesktop/el/impl/util/ReflectionUtil.java  |  209 +++
 src/org/jdesktop/el/impl/util/package.html         |   19 +
 src/org/jdesktop/el/package.html                   |  192 +++
 .../ObservableCollections.java                     |  363 +++++
 .../observablecollections/ObservableList.java      |   40 +
 .../ObservableListListener.java                    |   56 +
 .../observablecollections/ObservableMap.java       |   29 +
 .../ObservableMapListener.java                     |   42 +
 .../jdesktop/observablecollections/package.html    |   18 +
 .../jdesktop/swingbinding/ElementsProperty.java    |  146 ++
 .../jdesktop/swingbinding/JComboBoxBinding.java    |  393 +++++
 src/org/jdesktop/swingbinding/JListBinding.java    |  475 ++++++
 src/org/jdesktop/swingbinding/JTableBinding.java   |  841 ++++++++++
 src/org/jdesktop/swingbinding/SwingBindings.java   |  369 +++++
 .../adapters/AbstractButtonAdapterProvider.java    |   86 +
 .../swingbinding/adapters/BeanAdapterBase.java     |  115 ++
 .../adapters/JComboBoxAdapterProvider.java         |   87 +
 .../adapters/JListAdapterProvider.java             |  178 +++
 .../adapters/JSliderAdapterProvider.java           |  106 ++
 .../adapters/JSpinnerAdapterProvider.java          |   86 +
 .../adapters/JTableAdapterProvider.java            |  232 +++
 .../adapters/JTextComponentAdapterProvider.java    |  230 +++
 .../jdesktop/swingbinding/adapters/package.html    |   28 +
 .../swingbinding/impl/AbstractColumnBinding.java   |   35 +
 .../swingbinding/impl/ListBindingManager.java      |  269 ++++
 src/org/jdesktop/swingbinding/impl/package.html    |   20 +
 src/org/jdesktop/swingbinding/package.html         |  243 +++
 145 files changed, 25134 insertions(+), 0 deletions(-)

diff --git a/beansbinding-1.2.1-src.zip b/beansbinding-1.2.1-src.zip
deleted file mode 100644
index 2c337d5..0000000
Binary files a/beansbinding-1.2.1-src.zip and /dev/null differ
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..6b91f25
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- You may freely edit this file. See commented blocks below for -->
+<!-- some examples of how to customize the build. -->
+<!-- (If you delete it and reopen the project it will be recreated.) -->
+<project name="beansbinding" default="default" basedir="." xmlns:j2seproject3="http://www.netbeans.org/ns/j2se-project/3">
+    <description>Builds, tests, and runs the project beansbinding.</description>
+    <import file="nbproject/build-impl.xml"/>
+    <!--
+
+    There exist several targets which are by default empty and which can be 
+    used for execution of your tasks. These targets are usually executed 
+    before and after some main targets. They are: 
+
+      -pre-init:                 called before initialization of project properties
+      -post-init:                called after initialization of project properties
+      -pre-compile:              called before javac compilation
+      -post-compile:             called after javac compilation
+      -pre-compile-single:       called before javac compilation of single file
+      -post-compile-single:      called after javac compilation of single file
+      -pre-compile-test:         called before javac compilation of JUnit tests
+      -post-compile-test:        called after javac compilation of JUnit tests
+      -pre-compile-test-single:  called before javac compilation of single JUnit test
+      -post-compile-test-single: called after javac compilation of single JUunit test
+      -pre-jar:                  called before JAR building
+      -post-jar:                 called after JAR building
+      -post-clean:               called after cleaning build products
+
+    (Targets beginning with '-' are not intended to be called on their own.)
+
+    Example of inserting an obfuscator after compilation could look like this:
+
+        <target name="-post-compile">
+            <obfuscate>
+                <fileset dir="${build.classes.dir}"/>
+            </obfuscate>
+        </target>
+
+    For list of available properties check the imported 
+    nbproject/build-impl.xml file. 
+
+
+    Another way to customize the build is by overriding existing main targets.
+    The targets of interest are: 
+
+      -init-macrodef-javac:     defines macro for javac compilation
+      -init-macrodef-junit:     defines macro for junit execution
+      -init-macrodef-debug:     defines macro for class debugging
+      -init-macrodef-java:      defines macro for class execution
+      -do-jar-with-manifest:    JAR building (if you are using a manifest)
+      -do-jar-without-manifest: JAR building (if you are not using a manifest)
+      run:                      execution of project 
+      -javadoc-build:           Javadoc generation
+      test-report:              JUnit report generation
+
+    An example of overriding the target for project execution could look like this:
+
+        <target name="run" depends="binding3-impl.jar">
+            <exec dir="bin" executable="launcher.exe">
+                <arg file="${dist.jar}"/>
+            </exec>
+        </target>
+
+    Notice that the overridden target depends on the jar target and not only on 
+    the compile target as the regular run target does. Again, for a list of available 
+    properties which you can use, check the target you are overriding in the
+    nbproject/build-impl.xml file. 
+
+    -->
+
+  <target name="-post-init">
+    <property name="javac.debug" value="on"/>
+  </target>
+  <target name="dist" depends="javadoc,jar">
+    <property name="buildVersion" value="1.2.1"/>
+    <zip destfile="${dist.dir}/${ant.project.name}-${buildVersion}-doc.zip"
+         basedir="${dist.javadoc.dir}"/>
+    <zip destfile="${dist.dir}/${ant.project.name}-${buildVersion}-src.zip" >
+      <fileset dir="." >
+        <include name="${src.dir}/**" />
+      	<exclude name="**/CVS/**" />
+      </fileset>
+      <fileset dir=".">
+        <include name="nbproject/build-impl.xml" />
+        <include name="nbproject/project.xml" />
+        <include name="nbproject/genfiles.properties" />
+        <include name="nbproject/project.properties" />
+        <include name="build.xml" />
+        <include name="license.txt" />
+        <include name="releaseNotes.txt" />
+      </fileset>
+    </zip>
+    <copy file="${dist.dir}/${ant.project.name}.jar"
+          tofile="${dist.dir}/${ant.project.name}-${buildVersion}.jar"/>
+  </target>
+</project>
diff --git a/license.txt b/license.txt
new file mode 100644
index 0000000..fb45005
--- /dev/null
+++ b/license.txt
@@ -0,0 +1,511 @@
+Copyright (c) 2005-2007 Sun Microsystems, Inc., 4150 Network Circle, Santa
+Clara, California 95054, U.S.A. All rights reserved.  Use is subject
+to license terms below.  Sun, Sun Microsystems and the Sun logo are
+trademarks or registered trademarks of Sun Microsystems, Inc. in the
+U.S. and other countries.
+ 
+Notice: This product is covered by U.S. export control laws and may be
+subject to the export or import laws in other countries. These laws may
+restrict the fields of use for this software and may require you to
+secure government authorization.
+ 
+            GNU LESSER GENERAL PUBLIC LICENSE
+               Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+          GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+             END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice 
diff --git a/nbproject/build-impl.xml b/nbproject/build-impl.xml
new file mode 100644
index 0000000..b0d6bbe
--- /dev/null
+++ b/nbproject/build-impl.xml
@@ -0,0 +1,541 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+*** GENERATED FROM project.xml - DO NOT EDIT  ***
+***         EDIT ../build.xml INSTEAD         ***
+
+For the purpose of easier reading the script
+is divided into following sections:
+
+  - initialization
+  - compilation
+  - jar
+  - execution
+  - debugging
+  - javadoc
+  - junit compilation
+  - junit execution
+  - junit debugging
+  - applet
+  - cleanup
+
+-->
+<project name="beansbinding-impl" default="default" basedir=".." xmlns:j2seproject3="http://www.netbeans.org/ns/j2se-project/3" xmlns:j2seproject2="http://www.netbeans.org/ns/j2se-project/2" xmlns:j2seproject1="http://www.netbeans.org/ns/j2se-project/1" xmlns:jaxws="http://www.netbeans.org/ns/jax-ws/1">
+    <target name="default" depends="test,jar,javadoc" description="Build and test whole project."/>
+    <!-- 
+    ======================
+    INITIALIZATION SECTION 
+    ======================
+    -->
+    <target name="-pre-init">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target name="-init-private" depends="-pre-init">
+        <property file="nbproject/private/private.properties"/>
+    </target>
+    <target name="-init-user" depends="-pre-init,-init-private">
+        <property file="${user.properties.file}"/>
+        <!-- The two properties below are usually overridden -->
+        <!-- by the active platform. Just a fallback. -->
+        <property name="default.javac.source" value="1.4"/>
+        <property name="default.javac.target" value="1.4"/>
+    </target>
+    <target name="-init-project" depends="-pre-init,-init-private,-init-user">
+        <property file="nbproject/project.properties"/>
+    </target>
+    <target name="-do-init" depends="-pre-init,-init-private,-init-user,-init-project,-init-macrodef-property">
+        <available file="${manifest.file}" property="manifest.available"/>
+        <condition property="manifest.available+main.class">
+            <and>
+                <isset property="manifest.available"/>
+                <isset property="main.class"/>
+                <not>
+                    <equals arg1="${main.class}" arg2="" trim="true"/>
+                </not>
+            </and>
+        </condition>
+        <condition property="manifest.available+main.class+mkdist.available">
+            <and>
+                <istrue value="${manifest.available+main.class}"/>
+                <isset property="libs.CopyLibs.classpath"/>
+            </and>
+        </condition>
+        <condition property="have.tests">
+            <or>
+                <available file="${test.src.dir}"/>
+            </or>
+        </condition>
+        <condition property="have.sources">
+            <or>
+                <available file="${src.dir}"/>
+            </or>
+        </condition>
+        <condition property="netbeans.home+have.tests">
+            <and>
+                <isset property="netbeans.home"/>
+                <isset property="have.tests"/>
+            </and>
+        </condition>
+        <condition property="no.javadoc.preview">
+            <isfalse value="${javadoc.preview}"/>
+        </condition>
+        <property name="run.jvmargs" value=""/>
+        <property name="javac.compilerargs" value=""/>
+        <property name="work.dir" value="${basedir}"/>
+        <condition property="no.deps">
+            <and>
+                <istrue value="${no.dependencies}"/>
+            </and>
+        </condition>
+        <property name="javac.debug" value="true"/>
+        <property name="javadoc.preview" value="true"/>
+    </target>
+    <target name="-post-init">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target name="-init-check" depends="-pre-init,-init-private,-init-user,-init-project,-do-init">
+        <fail unless="src.dir">Must set src.dir</fail>
+        <fail unless="test.src.dir">Must set test.src.dir</fail>
+        <fail unless="build.dir">Must set build.dir</fail>
+        <fail unless="dist.dir">Must set dist.dir</fail>
+        <fail unless="build.classes.dir">Must set build.classes.dir</fail>
+        <fail unless="dist.javadoc.dir">Must set dist.javadoc.dir</fail>
+        <fail unless="build.test.classes.dir">Must set build.test.classes.dir</fail>
+        <fail unless="build.test.results.dir">Must set build.test.results.dir</fail>
+        <fail unless="build.classes.excludes">Must set build.classes.excludes</fail>
+        <fail unless="dist.jar">Must set dist.jar</fail>
+    </target>
+    <target name="-init-macrodef-property">
+        <macrodef name="property" uri="http://www.netbeans.org/ns/j2se-project/1">
+            <attribute name="name"/>
+            <attribute name="value"/>
+            <sequential>
+                <property name="@{name}" value="${@{value}}"/>
+            </sequential>
+        </macrodef>
+    </target>
+    <target name="-init-macrodef-javac">
+        <macrodef name="javac" uri="http://www.netbeans.org/ns/j2se-project/3">
+            <attribute name="srcdir" default="${src.dir}"/>
+            <attribute name="destdir" default="${build.classes.dir}"/>
+            <attribute name="classpath" default="${javac.classpath}"/>
+            <attribute name="debug" default="${javac.debug}"/>
+            <element name="customize" optional="true"/>
+            <sequential>
+                <javac srcdir="@{srcdir}" destdir="@{destdir}" debug="@{debug}" deprecation="${javac.deprecation}" source="${javac.source}" target="${javac.target}" includeantruntime="false">
+                    <classpath>
+                        <path path="@{classpath}"/>
+                    </classpath>
+                    <compilerarg line="${javac.compilerargs}"/>
+                    <customize/>
+                </javac>
+            </sequential>
+        </macrodef>
+    </target>
+    <target name="-init-macrodef-junit">
+        <macrodef name="junit" uri="http://www.netbeans.org/ns/j2se-project/3">
+            <attribute name="includes" default="**/*Test.java"/>
+            <sequential>
+                <junit showoutput="true" fork="true" dir="${basedir}" failureproperty="tests.failed" errorproperty="tests.failed">
+                    <batchtest todir="${build.test.results.dir}">
+                        <fileset dir="${test.src.dir}" includes="@{includes}"/>
+                    </batchtest>
+                    <classpath>
+                        <path path="${run.test.classpath}"/>
+                    </classpath>
+                    <syspropertyset>
+                        <propertyref prefix="test-sys-prop."/>
+                        <mapper type="glob" from="test-sys-prop.*" to="*"/>
+                    </syspropertyset>
+                    <formatter type="brief" usefile="false"/>
+                    <formatter type="xml"/>
+                    <jvmarg line="${run.jvmargs}"/>
+                </junit>
+            </sequential>
+        </macrodef>
+    </target>
+    <target name="-init-macrodef-nbjpda">
+        <macrodef name="nbjpdastart" uri="http://www.netbeans.org/ns/j2se-project/1">
+            <attribute name="name" default="${main.class}"/>
+            <attribute name="classpath" default="${debug.classpath}"/>
+            <attribute name="stopclassname" default=""/>
+            <sequential>
+                <nbjpdastart transport="dt_socket" addressproperty="jpda.address" name="@{name}" stopclassname="@{stopclassname}">
+                    <classpath>
+                        <path path="@{classpath}"/>
+                    </classpath>
+                </nbjpdastart>
+            </sequential>
+        </macrodef>
+        <macrodef name="nbjpdareload" uri="http://www.netbeans.org/ns/j2se-project/1">
+            <attribute name="dir" default="${build.classes.dir}"/>
+            <sequential>
+                <nbjpdareload>
+                    <fileset includes="${fix.includes}*.class" dir="@{dir}"/>
+                </nbjpdareload>
+            </sequential>
+        </macrodef>
+    </target>
+    <target name="-init-macrodef-debug">
+        <macrodef name="debug" uri="http://www.netbeans.org/ns/j2se-project/3">
+            <attribute name="classname" default="${main.class}"/>
+            <attribute name="classpath" default="${debug.classpath}"/>
+            <element name="customize" optional="true"/>
+            <sequential>
+                <java fork="true" classname="@{classname}" dir="${work.dir}">
+                    <jvmarg value="-Xdebug"/>
+                    <jvmarg value="-Xnoagent"/>
+                    <jvmarg value="-Djava.compiler=none"/>
+                    <jvmarg value="-Xrunjdwp:transport=dt_socket,address=${jpda.address}"/>
+                    <jvmarg line="${run.jvmargs}"/>
+                    <classpath>
+                        <path path="@{classpath}"/>
+                    </classpath>
+                    <syspropertyset>
+                        <propertyref prefix="run-sys-prop."/>
+                        <mapper type="glob" from="run-sys-prop.*" to="*"/>
+                    </syspropertyset>
+                    <customize/>
+                </java>
+            </sequential>
+        </macrodef>
+    </target>
+    <target name="-init-macrodef-java">
+        <macrodef name="java" uri="http://www.netbeans.org/ns/j2se-project/1">
+            <attribute name="classname" default="${main.class}"/>
+            <element name="customize" optional="true"/>
+            <sequential>
+                <java fork="true" classname="@{classname}" dir="${work.dir}">
+                    <jvmarg line="${run.jvmargs}"/>
+                    <classpath>
+                        <path path="${run.classpath}"/>
+                    </classpath>
+                    <syspropertyset>
+                        <propertyref prefix="run-sys-prop."/>
+                        <mapper type="glob" from="run-sys-prop.*" to="*"/>
+                    </syspropertyset>
+                    <customize/>
+                </java>
+            </sequential>
+        </macrodef>
+    </target>
+    <target name="-init-presetdef-jar">
+        <presetdef name="jar" uri="http://www.netbeans.org/ns/j2se-project/1">
+            <jar jarfile="${dist.jar}" compress="${jar.compress}">
+                <j2seproject1:fileset dir="${build.classes.dir}"/>
+            </jar>
+        </presetdef>
+    </target>
+    <target name="init" depends="-pre-init,-init-private,-init-user,-init-project,-do-init,-post-init,-init-check,-init-macrodef-property,-init-macrodef-javac,-init-macrodef-junit,-init-macrodef-nbjpda,-init-macrodef-debug,-init-macrodef-java,-init-presetdef-jar"/>
+    <!--
+    ===================
+    COMPILATION SECTION
+    ===================
+    -->
+    <target name="deps-jar" depends="init" unless="no.deps"/>
+    <target name="-pre-pre-compile" depends="init,deps-jar">
+        <mkdir dir="${build.classes.dir}"/>
+    </target>
+    <target name="-pre-compile">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target name="-do-compile" depends="init,deps-jar,-pre-pre-compile,-pre-compile" if="have.sources">
+        <j2seproject3:javac/>
+        <copy todir="${build.classes.dir}">
+            <fileset dir="${src.dir}" excludes="${build.classes.excludes}"/>
+        </copy>
+    </target>
+    <target name="-post-compile">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target name="compile" depends="init,deps-jar,-pre-pre-compile,-pre-compile,-do-compile,-post-compile" description="Compile project."/>
+    <target name="-pre-compile-single">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target name="-do-compile-single" depends="init,deps-jar,-pre-pre-compile">
+        <fail unless="javac.includes">Must select some files in the IDE or set javac.includes</fail>
+        <j2seproject3:javac>
+            <customize>
+                <patternset includes="${javac.includes}"/>
+            </customize>
+        </j2seproject3:javac>
+    </target>
+    <target name="-post-compile-single">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target name="compile-single" depends="init,deps-jar,-pre-pre-compile,-pre-compile-single,-do-compile-single,-post-compile-single"/>
+    <!--
+    ====================
+    JAR BUILDING SECTION
+    ====================
+    -->
+    <target name="-pre-pre-jar" depends="init">
+        <dirname property="dist.jar.dir" file="${dist.jar}"/>
+        <mkdir dir="${dist.jar.dir}"/>
+    </target>
+    <target name="-pre-jar">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target name="-do-jar-without-manifest" depends="init,compile,-pre-pre-jar,-pre-jar" unless="manifest.available">
+        <j2seproject1:jar/>
+    </target>
+    <target name="-do-jar-with-manifest" depends="init,compile,-pre-pre-jar,-pre-jar" if="manifest.available" unless="manifest.available+main.class">
+        <j2seproject1:jar manifest="${manifest.file}"/>
+    </target>
+    <target name="-do-jar-with-mainclass" depends="init,compile,-pre-pre-jar,-pre-jar" if="manifest.available+main.class" unless="manifest.available+main.class+mkdist.available">
+        <j2seproject1:jar manifest="${manifest.file}">
+            <j2seproject1:manifest>
+                <j2seproject1:attribute name="Main-Class" value="${main.class}"/>
+            </j2seproject1:manifest>
+        </j2seproject1:jar>
+        <echo>To run this application from the command line without Ant, try:</echo>
+        <property name="build.classes.dir.resolved" location="${build.classes.dir}"/>
+        <property name="dist.jar.resolved" location="${dist.jar}"/>
+        <pathconvert property="run.classpath.with.dist.jar">
+            <path path="${run.classpath}"/>
+            <map from="${build.classes.dir.resolved}" to="${dist.jar.resolved}"/>
+        </pathconvert>
+        <echo>java -cp "${run.classpath.with.dist.jar}" ${main.class}</echo>
+    </target>
+    <target name="-do-jar-with-libraries" depends="init,compile,-pre-pre-jar,-pre-jar" if="manifest.available+main.class+mkdist.available">
+        <property name="build.classes.dir.resolved" location="${build.classes.dir}"/>
+        <pathconvert property="run.classpath.without.build.classes.dir">
+            <path path="${run.classpath}"/>
+            <map from="${build.classes.dir.resolved}" to=""/>
+        </pathconvert>
+        <pathconvert property="jar.classpath" pathsep=" ">
+            <path path="${run.classpath.without.build.classes.dir}"/>
+            <chainedmapper>
+                <flattenmapper/>
+                <globmapper from="*" to="lib/*"/>
+            </chainedmapper>
+        </pathconvert>
+        <taskdef classname="org.netbeans.modules.java.j2seproject.copylibstask.CopyLibs" name="copylibs" classpath="${libs.CopyLibs.classpath}"/>
+        <copylibs manifest="${manifest.file}" runtimeclasspath="${run.classpath.without.build.classes.dir}" jarfile="${dist.jar}" compress="${jar.compress}">
+            <fileset dir="${build.classes.dir}"/>
+            <manifest>
+                <attribute name="Main-Class" value="${main.class}"/>
+                <attribute name="Class-Path" value="${jar.classpath}"/>
+            </manifest>
+        </copylibs>
+        <echo>To run this application from the command line without Ant, try:</echo>
+        <property name="dist.jar.resolved" location="${dist.jar}"/>
+        <echo>java -jar "${dist.jar.resolved}"</echo>
+    </target>
+    <target name="-post-jar">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target name="jar" depends="init,compile,-pre-jar,-do-jar-with-manifest,-do-jar-without-manifest,-do-jar-with-mainclass,-do-jar-with-libraries,-post-jar" description="Build JAR."/>
+    <!--
+    =================
+    EXECUTION SECTION
+    =================
+    -->
+    <target name="run" depends="init,compile" description="Run a main class.">
+        <j2seproject1:java>
+            <customize>
+                <arg line="${application.args}"/>
+            </customize>
+        </j2seproject1:java>
+    </target>
+    <target name="run-single" depends="init,compile-single">
+        <fail unless="run.class">Must select one file in the IDE or set run.class</fail>
+        <j2seproject1:java classname="${run.class}"/>
+    </target>
+    <!--
+    =================
+    DEBUGGING SECTION
+    =================
+    -->
+    <target name="-debug-start-debugger" if="netbeans.home" depends="init">
+        <j2seproject1:nbjpdastart name="${debug.class}"/>
+    </target>
+    <target name="-debug-start-debuggee" depends="init,compile">
+        <j2seproject3:debug>
+            <customize>
+                <arg line="${application.args}"/>
+            </customize>
+        </j2seproject3:debug>
+    </target>
+    <target name="debug" if="netbeans.home" depends="init,compile,-debug-start-debugger,-debug-start-debuggee" description="Debug project in IDE."/>
+    <target name="-debug-start-debugger-stepinto" if="netbeans.home" depends="init">
+        <j2seproject1:nbjpdastart stopclassname="${main.class}"/>
+    </target>
+    <target name="debug-stepinto" if="netbeans.home" depends="init,compile,-debug-start-debugger-stepinto,-debug-start-debuggee"/>
+    <target name="-debug-start-debuggee-single" if="netbeans.home" depends="init,compile-single">
+        <fail unless="debug.class">Must select one file in the IDE or set debug.class</fail>
+        <j2seproject3:debug classname="${debug.class}"/>
+    </target>
+    <target name="debug-single" if="netbeans.home" depends="init,compile-single,-debug-start-debugger,-debug-start-debuggee-single"/>
+    <target name="-pre-debug-fix" depends="init">
+        <fail unless="fix.includes">Must set fix.includes</fail>
+        <property name="javac.includes" value="${fix.includes}.java"/>
+    </target>
+    <target name="-do-debug-fix" if="netbeans.home" depends="init,-pre-debug-fix,compile-single">
+        <j2seproject1:nbjpdareload/>
+    </target>
+    <target name="debug-fix" if="netbeans.home" depends="init,-pre-debug-fix,-do-debug-fix"/>
+    <!--
+    ===============
+    JAVADOC SECTION
+    ===============
+    -->
+    <target name="-javadoc-build" depends="init">
+        <mkdir dir="${dist.javadoc.dir}"/>
+        <javadoc destdir="${dist.javadoc.dir}" source="${javac.source}" notree="${javadoc.notree}" use="${javadoc.use}" nonavbar="${javadoc.nonavbar}" noindex="${javadoc.noindex}" splitindex="${javadoc.splitindex}" author="${javadoc.author}" version="${javadoc.version}" windowtitle="${javadoc.windowtitle}" private="${javadoc.private}" additionalparam="${javadoc.additionalparam}" failonerror="true" useexternalfile="true">
+            <classpath>
+                <path path="${javac.classpath}"/>
+            </classpath>
+            <sourcepath>
+                <pathelement location="${src.dir}"/>
+            </sourcepath>
+            <packageset dir="${src.dir}" includes="*/**"/>
+            <fileset dir="${src.dir}" includes="*.java"/>
+        </javadoc>
+    </target>
+    <target name="-javadoc-browse" if="netbeans.home" unless="no.javadoc.preview" depends="init,-javadoc-build">
+        <nbbrowse file="${dist.javadoc.dir}/index.html"/>
+    </target>
+    <target name="javadoc" depends="init,-javadoc-build,-javadoc-browse" description="Build Javadoc."/>
+    <!--
+    =========================
+    JUNIT COMPILATION SECTION
+    =========================
+    -->
+    <target name="-pre-pre-compile-test" if="have.tests" depends="init,compile">
+        <mkdir dir="${build.test.classes.dir}"/>
+    </target>
+    <target name="-pre-compile-test">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target name="-do-compile-test" if="have.tests" depends="init,compile,-pre-pre-compile-test,-pre-compile-test">
+        <j2seproject3:javac srcdir="${test.src.dir}" destdir="${build.test.classes.dir}" debug="true" classpath="${javac.test.classpath}"/>
+        <copy todir="${build.test.classes.dir}">
+            <fileset dir="${test.src.dir}" excludes="**/*.java"/>
+        </copy>
+    </target>
+    <target name="-post-compile-test">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target name="compile-test" depends="init,compile,-pre-pre-compile-test,-pre-compile-test,-do-compile-test,-post-compile-test"/>
+    <target name="-pre-compile-test-single">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target name="-do-compile-test-single" if="have.tests" depends="init,compile,-pre-pre-compile-test,-pre-compile-test-single">
+        <fail unless="javac.includes">Must select some files in the IDE or set javac.includes</fail>
+        <j2seproject3:javac srcdir="${test.src.dir}" destdir="${build.test.classes.dir}" debug="true" classpath="${javac.test.classpath}">
+            <customize>
+                <patternset includes="${javac.includes}"/>
+            </customize>
+        </j2seproject3:javac>
+        <copy todir="${build.test.classes.dir}">
+            <fileset dir="${test.src.dir}" excludes="**/*.java"/>
+        </copy>
+    </target>
+    <target name="-post-compile-test-single">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target name="compile-test-single" depends="init,compile,-pre-pre-compile-test,-pre-compile-test-single,-do-compile-test-single,-post-compile-test-single"/>
+    <!--
+    =======================
+    JUNIT EXECUTION SECTION
+    =======================
+    -->
+    <target name="-pre-test-run" if="have.tests" depends="init">
+        <mkdir dir="${build.test.results.dir}"/>
+    </target>
+    <target name="-do-test-run" if="have.tests" depends="init,compile-test,-pre-test-run">
+        <j2seproject3:junit/>
+    </target>
+    <target name="-post-test-run" if="have.tests" depends="init,compile-test,-pre-test-run,-do-test-run">
+        <fail if="tests.failed">Some tests failed; see details above.</fail>
+    </target>
+    <target name="test-report" if="have.tests" depends="init"/>
+    <target name="-test-browse" if="netbeans.home+have.tests" depends="init"/>
+    <target name="test" depends="init,compile-test,-pre-test-run,-do-test-run,test-report,-post-test-run,-test-browse" description="Run unit tests."/>
+    <target name="-pre-test-run-single" if="have.tests" depends="init">
+        <mkdir dir="${build.test.results.dir}"/>
+    </target>
+    <target name="-do-test-run-single" if="have.tests" depends="init,compile-test-single,-pre-test-run-single">
+        <fail unless="test.includes">Must select some files in the IDE or set test.includes</fail>
+        <j2seproject3:junit includes="${test.includes}"/>
+    </target>
+    <target name="-post-test-run-single" if="have.tests" depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single">
+        <fail if="tests.failed">Some tests failed; see details above.</fail>
+    </target>
+    <target name="test-single" depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single,-post-test-run-single" description="Run single unit test."/>
+    <!--
+    =======================
+    JUNIT DEBUGGING SECTION
+    =======================
+    -->
+    <target name="-debug-start-debuggee-test" if="have.tests" depends="init,compile-test">
+        <fail unless="test.class">Must select one file in the IDE or set test.class</fail>
+        <j2seproject3:debug classname="junit.textui.TestRunner" classpath="${debug.test.classpath}">
+            <customize>
+                <arg line="${test.class}"/>
+            </customize>
+        </j2seproject3:debug>
+    </target>
+    <target name="-debug-start-debugger-test" if="netbeans.home+have.tests" depends="init,compile-test">
+        <j2seproject1:nbjpdastart name="${test.class}" classpath="${debug.test.classpath}"/>
+    </target>
+    <target name="debug-test" depends="init,compile-test,-debug-start-debugger-test,-debug-start-debuggee-test"/>
+    <target name="-do-debug-fix-test" if="netbeans.home" depends="init,-pre-debug-fix,compile-test-single">
+        <j2seproject1:nbjpdareload dir="${build.test.classes.dir}"/>
+    </target>
+    <target name="debug-fix-test" if="netbeans.home" depends="init,-pre-debug-fix,-do-debug-fix-test"/>
+    <!--
+    =========================
+    APPLET EXECUTION SECTION
+    =========================
+    -->
+    <target name="run-applet" depends="init,compile-single">
+        <fail unless="applet.url">Must select one file in the IDE or set applet.url</fail>
+        <j2seproject1:java classname="sun.applet.AppletViewer">
+            <customize>
+                <arg value="${applet.url}"/>
+            </customize>
+        </j2seproject1:java>
+    </target>
+    <!--
+    =========================
+    APPLET DEBUGGING  SECTION
+    =========================
+    -->
+    <target name="-debug-start-debuggee-applet" if="netbeans.home" depends="init,compile-single">
+        <fail unless="applet.url">Must select one file in the IDE or set applet.url</fail>
+        <j2seproject3:debug classname="sun.applet.AppletViewer">
+            <customize>
+                <arg value="${applet.url}"/>
+            </customize>
+        </j2seproject3:debug>
+    </target>
+    <target name="debug-applet" if="netbeans.home" depends="init,compile-single,-debug-start-debugger,-debug-start-debuggee-applet"/>
+    <!--
+    ===============
+    CLEANUP SECTION
+    ===============
+    -->
+    <target name="deps-clean" depends="init" unless="no.deps"/>
+    <target name="-do-clean" depends="init">
+        <delete dir="${build.dir}"/>
+        <delete dir="${dist.dir}"/>
+    </target>
+    <target name="-post-clean">
+        <!-- Empty placeholder for easier customization. -->
+        <!-- You can override this target in the ../build.xml file. -->
+    </target>
+    <target name="clean" depends="init,deps-clean,-do-clean,-post-clean" description="Clean build products."/>
+</project>
diff --git a/nbproject/genfiles.properties b/nbproject/genfiles.properties
new file mode 100644
index 0000000..c8be1f2
--- /dev/null
+++ b/nbproject/genfiles.properties
@@ -0,0 +1,8 @@
+build.xml.data.CRC32=549778cb
+build.xml.script.CRC32=b87cd82f
+build.xml.stylesheet.CRC32=d5b6853a
+# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
+# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
+nbproject/build-impl.xml.data.CRC32=0971d807
+nbproject/build-impl.xml.script.CRC32=d9c2ba51
+nbproject/build-impl.xml.stylesheet.CRC32=65d7ca21
diff --git a/nbproject/project.properties b/nbproject/project.properties
new file mode 100644
index 0000000..c6490ad
--- /dev/null
+++ b/nbproject/project.properties
@@ -0,0 +1,56 @@
+application.args=
+build.classes.dir=${build.dir}/classes
+build.classes.excludes=**/*.java,**/*.form,**/*.html
+# This directory is removed when the project is cleaned:
+build.dir=build
+build.generated.dir=${build.dir}/generated
+# Only compile against the classpath explicitly listed here:
+build.sysclasspath=ignore
+build.test.classes.dir=${build.dir}/test/classes
+build.test.results.dir=${build.dir}/test/results
+debug.classpath=\
+    ${run.classpath}
+debug.test.classpath=\
+    ${run.test.classpath}
+# This directory is removed when the project is cleaned:
+dist.dir=dist
+dist.jar=${dist.dir}/beansbinding.jar
+dist.javadoc.dir=${dist.dir}/javadoc
+file.reference.el.jar=lib/el.jar
+jar.compress=false
+javac.classpath=
+javac.compilerargs=-target 1.5
+# Space-separated list of extra javac options
+#javac.compilerargs=-Xlint:unchecked
+javac.deprecation=false
+javac.source=1.5
+javac.target=1.5
+javac.test.classpath=\
+    ${javac.classpath}:\
+    ${build.classes.dir}:\
+    ${libs.junit.classpath}
+javadoc.additionalparam=
+javadoc.author=false
+javadoc.encoding=
+javadoc.noindex=false
+javadoc.nonavbar=false
+javadoc.notree=false
+javadoc.private=false
+javadoc.splitindex=true
+javadoc.use=true
+javadoc.version=false
+javadoc.windowtitle=Beans Binding
+meta.inf.dir=${src.dir}/META-INF
+platform.active=default_platform
+run.classpath=\
+    ${javac.classpath}:\
+    ${build.classes.dir}
+# Space-separated list of JVM arguments used when running the project
+# (you may also define separate properties like run-sys-prop.name=value instead of -Dname=value
+# or test-sys-prop.name=value to set system properties for unit tests):
+run.jvmargs=-ea -esa
+run.test.classpath=\
+    ${javac.test.classpath}:\
+    ${build.test.classes.dir}
+src.dir=src
+test.src.dir=test
diff --git a/nbproject/project.xml b/nbproject/project.xml
new file mode 100644
index 0000000..a8a9e91
--- /dev/null
+++ b/nbproject/project.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://www.netbeans.org/ns/project/1">
+    <type>org.netbeans.modules.java.j2seproject</type>
+    <configuration>
+        <data xmlns="http://www.netbeans.org/ns/j2se-project/3">
+            <name>beansbinding</name>
+            <minimum-ant-version>1.6.5</minimum-ant-version>
+            <source-roots>
+                <root id="src.dir"/>
+            </source-roots>
+            <test-roots>
+                <root id="test.src.dir"/>
+            </test-roots>
+        </data>
+        <references xmlns="http://www.netbeans.org/ns/ant-project-references/1"/>
+    </configuration>
+</project>
diff --git a/releaseNotes.txt b/releaseNotes.txt
new file mode 100644
index 0000000..fc30902
--- /dev/null
+++ b/releaseNotes.txt
@@ -0,0 +1,252 @@
+Release notes for Beans Binding (http://beansbinding.dev.java.net)
+
+
+1.2.1 Release (2007/11/02)
+--------------------------
+This is a bug fix release, solving the following issues:
+
+Issue 16: JTable/JListAdapterProvider: don't re-wire listening to selectionModel on change
+Issue 19: clear() on an empty ObservableList bound to JList/JTable causes exception
+Issue 20: AbstractButtonAdapterProvider.Adapter.cachedSelected
+
+
+1.2 Release (2007/10/29)
+--------------------------
+This release contains some minor API changes and bug fixes.
+
+API changes:
+
+- The hasEditedSource and hasEditedTarget properties have been removed from
+  Binding and AutoBinding, as have the hasEditedTargetBindings property and
+  getEditedTargetBindings() method from BindingGroup. These properties and
+  methods, which existed to assist in tracking which Bindings require saving,
+  are to be replaced with a more robust system of notifications and methods.
+
+- The semantics of Binding's sourceUnreadableValue property has changed such
+  that it is only used if explicity set by the developer. A new SyncFailure
+  type, SOURCE_UNREADABLE, has been added to indicate an attempt to refresh
+  with an unreadable source and no sourceUnreadableValue set.
+
+- BindingListener's sourceEdited and targetEdited methods have been deprecated
+  and replaced with sourceChanged and targetChanged methods that provide the
+  originating PropertyStateEvent.
+
+- BindingListener's syncFailed method has been changed to take a single
+  SyncFailure rather than a varargs. The original signature was designed to
+  support AutoBinding's current update strategies, which have been deemed
+  too complex and will be simplified in a future update.
+
+Issues Resolved:
+
+Issue 15: MissingResourceException on wrong ELProperty
+Issue 4: targetEdited not fired when changing a JTable cell
+
+
+1.1.1 Release (2007/10/18)
+--------------------------
+This release contains one bug fix, for the following issue:
+
+Issue 12: sourceUnreadableValue/sourceNullValue ignored for
+          JTableBinding.ColumnBinding and JListBinding.DetailBinding
+
+
+1.1 Release (2007/10/17)
+--------------------------
+This release provides a major performance improvement over version 1.0,
+a small set of bug fixes, and the addition of support for binding to JSpinner's
+value property.
+
+An extreme difference in performance can be seen with BeanProperty and
+ELProperty in this version. As these classes are central to binding, the result
+is that most things now perform MUCH faster. Consider the following simple
+testcase:
+
+    Person person = new Person(10, "John", "Smith", true, 30);
+    Property p = BeanProperty.create("firstName");
+    p.addPropertyStateListener(person, new PSL());
+    long start = System.currentTimeMillis();
+    for (int i = 0; i < 100000; i++) {
+        p.getValue(person);
+    }
+    System.out.println(System.currentTimeMillis() - start);
+
+In 1.0, this test takes approximately 13,100 milliseconds on my fast machine.
+On the contrary, this test takes approximately 8 milliseconds in 1.1!
+
+In addition to the performance improvements, the following were fixed or enhanced:
+
+- Added support for binding to a JSpinner's value property
+- JSlider's "value_IGNORE_ADJUSTING" property, which was broken in 1.0, has been fixed
+- Fixed Issue 8: Typo in removeTableModelListener
+- Fixed Issue 10: Binding does not work after updating a target value with JList/JTable/JComboBox
+- Fixed Issue 11: Setting convertor on ColumnBinding dosn't work
+- Other small tweaks and fixes
+
+
+1.0 Release (2007/09/05)
+--------------------------
+This release represents a major re-architecture of the Beans Binding API, based
+on feedback from members of the expert group and the community (Thank You!).
+While the work on this project continues through the JCP process,
+with feature additions and possible API changes to come, the current
+state represents the core API that is expected, for the most part, to
+persist. As such, we're calling it 1.0 and releasing!
+
+Some of the major points of interest in this release:
+
+  1) The concept of a property has been factored out into an abstract Property class,
+     with two concrete implementations of interest: BeanProperty and ELProperty.
+
+  2) Binding is now an abstract class representing a binding between two Property
+     instances (typically associated with two objects).
+
+  3) Binding with automatic syncing is implemented by a new concrete AutoBinding subclass.
+
+  4) Bindings to complex Swing components (such as JTable, JList and JComboBox) are
+     now handled by custom Binding subclasses.
+
+  5) For those synthetic Swing properties that we expose via adapters, those with
+     multiple possible behaviors are exposed as multiple synthetic properties.
+     For example: "text", "text_ON_FOCUS_LOST" and "text_ON_ACTION_OR_FOCUS_LOST"
+     for JTextField; "selectedElement" and "selectedElement_IGNORE_ADJUSTING" for
+     JList and JTable.
+
+  6) Everything has been repackaged into org.jdesktop packages.
+
+Please note that the process of updating the project's unit test suite for the
+re-architecture has not yet been completed. As such, the majority of unit tests
+are not compileable or runnable.
+
+
+0.6.1 Release (2007/06/22)
+--------------------------
+The purpose of this release is a set of name changes that make things
+more intuitive and easier to use.
+
+API changes:
+
+  1) UpdateStrategy.READ_FROM_SOURCE has been renamed to the shorter
+     UpdateStrategy.READ
+
+  2) TextChangeStategy is now a top-level enum. In addition, the values
+     CHANGE_ON_TYPE, CHANGE_ON_ACTION_OR_FOCUS_LOST and CHANGE_ON_FOCUS_LOST
+     have been shortened to ON_TYPE, ON_ACTION_OR_FOCUS_LOST, ON_FOCUS_LOST.
+
+  3) Binding.Parameter has been renamed to Binding.ParameterKey and
+     Binding.setValue/getValue have been given the more appropriate names
+     Binding.putParameter/getParameter.
+
+  4) All Swing ParameterKeys are now in a top-level ParameterKeys class.
+     They've been renamed as appropriate to reflect the fact that
+     they are constants. A few have also been shortened.
+
+  5) SwingBindingSupport has been removed. Documentation for Swing binding
+     is now in the package-level documentation.
+
+To see how some of these changes affect you, let's look at two examples:
+
+Before:
+
+Binding b = new Binding(list, null, table, "elements");
+b.addChildBinding("${firstName}, null)
+    .setValue(SwingBindingSupport.TableColumnParameter, 0)
+    .setValue(SwingBindingSupport.TableColumnClassParameter, String.class);
+
+After:
+
+Binding b = new Binding(list, null, table, "elements");
+b.addChildBinding("${firstName}, null)
+    .putParameter(ParameterKeys.COLUMN, 0)
+    .putParameter(ParameterKeys.COLUMN_CLASS, String.class);
+
+Likewise, before:
+
+Binding b = new Binding(object, "${property}", textField, "text");
+b.setValue(SwingBindingSupport.TextChangeStrategyParameter,
+           SwingBindingSupport.TextChangeStrategy.CHANGE_ON_TYPE);
+
+After:
+
+Binding b = new Binding(object, "${property}", textField, "text");
+binding.putParameter(ParameterKeys.TEXT_CHANGE_STRATEGY,
+                     TextChangeStrategy.ON_TYPE);
+
+
+0.6 Release (2007/06/20)
+--------------------------
+Notes on this release are broken into the following sections:
+  API Changes/Additions, Issues Resolved, Other
+
+*** API Changes/Additions ***
+
+  1) Binding.addBinding/removeBinding/getBindings have been renamed to the more
+     descriptive Binding.addChildBinding/removeChildBinding/getChildBindings
+
+  2) Binding.setSourcePath/getSourcePath have been renamed to the more
+     appropriate Binding.setSourceExpression/getSourceExpression
+
+  3) To enforce compile-time type safety, the Object varargs parameter has been
+     removed from all constructors and methods in Binding and BindingContext.
+     You must now call setValue directly. To allow for method chaining, setValue
+     now returns the Binding. As an example, replace this Binding:
+
+     Binding b = new Binding(source, "${property}", target, "property",
+                             Parameter1, param1Value, Parameter2, param2Value);
+
+     with this:
+
+     Binding b = new Binding(source, "${property}", target, "property");
+     b.setValue(Parameter1, param1Value).setValue(Parameter2, param2Value);
+
+  4) Some Binding and BindingContext methods were updated to throw the more
+     appropriate IllegalArgumentException (rather than IllegalStateException)
+     for certain conditions.
+
+  5) Binding now has a name property. The ability to name a binding assists in
+     debugging. Its main goal, however, is to make it possible to fetch
+     bindings by name. This will show its full utility with future changes
+     making it easier to validate and then commit or revert bindings as a group.
+
+     To support naming a Binding, the following API additions have been
+     made to Binding:
+       - Constructors that take a String name parameter
+       - void setName(String name)
+       - String getName()
+       - addChildBinding methods that take a String name parameter
+       - Binding getChildBinding(String name)
+     In addition, the following additions have been made to BindingContext:
+       - addBinding methods that take a String name parameter
+       - Binding getBinding(String name)
+
+  6) Added a Parameter to control the editability of a JTable when it is the
+     target of a binding. A new EditableParameter has been added to
+     SwingBindingSupport to control this. It can be used on a top-level
+     binding to control the editability of the entire JTable, and/or on the
+     binding's individual child bindings to control editability of individual
+     columns. For example, to make all columns non-editable, except for
+     the first:
+
+     Binding b = new Binding(list, null, table, "elements");
+     // whole table is non-editable
+     b.setValue(SwingBindingSupport.EditableParameter, false);
+     b.addChildBinding("${firstName}, null)
+         .setValue(SwingBindingSupport.TableColumnParameter, 0)
+         // this column IS editable
+         .setValue(SwingBindingSupport.EditableParameter, true);
+     b.addChildBinding("${lastName}, null)
+         .setValue(SwingBindingSupport.TableColumnParameter, 1);
+
+*** Issues Resolved ***
+
+  2: Need converters between various types and String
+  5: JTable binding support doesn't support sorting and filtering
+
+*** Other ***
+
+  The JavaDoc in Binding and SwingBindingSupport has been updated to reflect the
+  change to use EL for the source "property".
+
+
+0.5 Release (2007/04/02)
+--------------------------
+Initial public release.
diff --git a/src/META-INF/services/org.jdesktop.beansbinding.ext.BeanAdapterProvider b/src/META-INF/services/org.jdesktop.beansbinding.ext.BeanAdapterProvider
new file mode 100644
index 0000000..8fb4e43
--- /dev/null
+++ b/src/META-INF/services/org.jdesktop.beansbinding.ext.BeanAdapterProvider
@@ -0,0 +1,10 @@
+# Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+# subject to license terms.
+
+org.jdesktop.swingbinding.adapters.AbstractButtonAdapterProvider
+org.jdesktop.swingbinding.adapters.JTextComponentAdapterProvider
+org.jdesktop.swingbinding.adapters.JListAdapterProvider
+org.jdesktop.swingbinding.adapters.JTableAdapterProvider
+org.jdesktop.swingbinding.adapters.JSliderAdapterProvider
+org.jdesktop.swingbinding.adapters.JSpinnerAdapterProvider
+org.jdesktop.swingbinding.adapters.JComboBoxAdapterProvider
diff --git a/src/org/jdesktop/beansbinding/AbstractBindingListener.java b/src/org/jdesktop/beansbinding/AbstractBindingListener.java
new file mode 100644
index 0000000..2c5d40f
--- /dev/null
+++ b/src/org/jdesktop/beansbinding/AbstractBindingListener.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.beansbinding;
+
+/**
+ * An abstract subclass of {@code BindingListener} that simplifies writing
+ * {@code BindingListeners} by allowing you to extend this class and re-implement
+ * only the methods you care about.
+ *
+ * @author Shannon Hickey
+ */
+public abstract class AbstractBindingListener implements BindingListener {
+
+    /**
+     * {@inheritDoc}
+     */
+    public void bindingBecameBound(Binding binding) {}
+
+    /**
+     * {@inheritDoc}
+     */
+    public void bindingBecameUnbound(Binding binding) {}
+
+    /**
+     * {@inheritDoc}
+     */
+    public void syncFailed(Binding binding, Binding.SyncFailure failure) {}
+
+    /**
+     * {@inheritDoc}
+     */
+    public void synced(Binding binding) {}
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * This implementation calls {@code sourceEdited} if the provided event returns
+     * {@code true} from {@code getValueChanged}.
+     */
+    public void sourceChanged(Binding binding, PropertyStateEvent event) {
+        if (event.getValueChanged()) {
+            sourceEdited(binding);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * This implementation calls {@code targetEdited} if the provided event returns
+     * {@code true} from {@code getValueChanged}.
+     */
+    public void targetChanged(Binding binding, PropertyStateEvent event) {
+        if (event.getValueChanged()) {
+            targetEdited(binding);
+        }
+    }
+    
+    /**
+     * Notification that the source property of a {@code Binding} has fired
+     * a {@code PropertyStateEvent} indicating that its <b>value or readability</b>
+     * has changed for the {@code Binding's} source object. Called by the default
+     * {@code AbstractBindingListener's} implementation of {@code sourceChanged}.
+     *
+     * @param binding the {@code Binding}
+     * @deprecated This method has been replaced by {@link #sourceChanged} and it
+     *             will go away soon. It is being kept for a short period only,
+     *             to assist in migration.
+     */
+    @Deprecated
+    public void sourceEdited(Binding binding) {}
+
+    /**
+     * Notification that the target property of a {@code Binding} has fired
+     * a {@code PropertyStateEvent} indicating that its <b>value or readability</b>
+     * has changed for the {@code Binding's} target object. Called by the default
+     * {@code AbstractBindingListener's} implementation of {@code targetChanged}.
+     *
+     * @param binding the {@code Binding}
+     * @deprecated This method has been replaced by {@link #targetChanged} and it
+     *             will go away soon. It is being kept for a short period only,
+     *             to assist in migration.
+     */
+    @Deprecated
+    public void targetEdited(Binding binding) {}
+
+}
diff --git a/src/org/jdesktop/beansbinding/AutoBinding.java b/src/org/jdesktop/beansbinding/AutoBinding.java
new file mode 100644
index 0000000..95cbe25
--- /dev/null
+++ b/src/org/jdesktop/beansbinding/AutoBinding.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.beansbinding;
+
+/**
+ * An implementation of {@code Binding} that automatically syncs the source
+ * and target by refreshing and saving according to one of three update
+ * strategies. The update strategy is specified for an {@code AutoBinding}
+ * on creation, and is one of:
+ * <p>
+ * <ul>
+ *     <li>{@code AutoBinding.UpdateStrategy.READ_ONCE}</li>
+ *     <li>{@code AutoBinding.UpdateStrategy.READ}</li>
+ *     <li>{@code AutoBinding.UpdateStrategy.READ_WRITE}</li>
+ * </ul>
+ * <p>
+ * <a name="STRATEGY_BEHAVIOR">The behavior</a> of {@code AutoBinding} for each
+ * of the update strategies is defined as follows:
+ * <p>
+ * <table border="0">
+ * <tr valign="baseline">
+ *   <td><b><font size="+1">{@code READ_ONCE}</font></b></td>
+ *   <td>&nbsp;&nbsp;&nbsp;</td>
+ *   <td>
+ *     <b>Summary:</b><br>
+ *     Tries to sync the target from the source only once, at bind time.
+ *     <p>
+ *     <b>Details:</b><br>
+ *     At bind time, tries to sync the target from the source, by calling
+ *     {@code refreshAndNotify}. No further automatic syncing is done.
+ *   </td>
+ * </tr>
+ * <tr><td colspan="3"><br></td></tr>
+ * <tr valign="baseline">
+ *   <td><b><font size="+1">{@code READ}</font></b></td>
+ *   <td>&nbsp;&nbsp;&nbsp;</td>
+ *   <td>
+ *     <b>Summary:</b><br>
+ *     Tries to keep the target in sync with the source.
+ *     <p>
+ *     <b>Details:</b><br>
+ *     At bind time, tries to sync the target from the source, by calling
+ *     {@code refreshAndNotify}. Then automatically tries to sync the target
+ *     from the source by calling {@code refreshAndNotify} when either the source
+ *     changes value, or the target changes from unwriteable to writeable.
+ *   </td>
+ * </tr>
+ * <tr><td colspan="3"><br></td></tr>
+ * <tr valign="baseline">
+ *   <td><b><font size="+1">{@code READ_WRITE}</font></b></td>
+ *   <td>&nbsp;&nbsp;&nbsp;</td>
+ *   <td>
+ *     <b>Summary:</b><br>
+ *     Tries to keep both the source and target in sync with each other.
+ *     <p>
+ *     <b>Details:</b><br>
+ *     At bind time, first tries to sync the target from the source, by calling
+ *     {@code refresh}. If the call succeeds, notifies the binding listeners
+ *     of a successful sync. If the call returns failure, then tries to instead sync the
+ *     source from the target by calling {@code save}. If this second call succeeds,
+ *     notifies the binding listeners of a succesful sync. If it returns failure, notifies
+ *     the binding listeners of a failed sync, indicating the reason for the original
+ *     refresh failure.
+ *     <p>
+ *     Automatically responds to changes in the state of the source as follows:
+ *     If the change represents a value change, use the try-refresh-then-save
+ *     procedure mentioned above. Otherwise, if the change represents the
+ *     source becoming writeable, tries to update the source from the target
+ *     by calling {@code saveAndNotify}.
+ *     <p>
+ *     Automatically responds to changes in the state of the target as follows:
+ *     If the change represents the target simply becoming writeable, try to
+ *     sync the target from the source by calling {@code refreshAndNotify}. If
+ *     the change represents the target becoming writeable and the value changing
+ *     together, use the try-refresh-then-save procedure mentioned above. Finally
+ *     if the change represents the target's value changing alone, first try to
+ *     sync the source from the target by calling {@code save}.
+ *     If that succeeds, notify the listeners of a successful sync. If it
+ *     returns failure due to conversion or validation, notify the listeners of a sync
+ *     failure, providing the conversion or validation failure. If it fails for
+ *     any other reason, then instead try to sync the target from the source by
+ *     calling {@code refresh}. If this succeeds, notify the listeners of successful
+ *     sync. Otherwise notify them of failure with the reasons for the original
+ *     save failure.
+ *   </td>
+ * </tr>
+ * </table>
+ *
+ * @param <SS> the type of source object
+ * @param <SV> the type of value that the source property represents
+ * @param <TS> the type of target object
+ * @param <TV> the type of value that the target property represents
+ *
+ * @author Shannon Hickey
+ */
+public class AutoBinding<SS, SV, TS, TV> extends Binding<SS, SV, TS, TV> {
+
+    private UpdateStrategy strategy;
+
+    /**
+     * An enumeration representing the possible update strategies of an
+     * {@code AutoBinding}. See {@code AutoBinding's} class level
+     * <a href="AutoBinding.html#STRATEGY_BEHAVIOR">documentation</a> for complete
+     * details on the sync behavior for each possible update strategy.
+     */
+    public enum UpdateStrategy {
+
+        /**
+         * An update strategy where the {@code AutoBinding} tries to sync the
+         * target from the source only once, at bind time.
+         */
+        READ_ONCE,
+
+        /**
+         * An update strategy where the {@code AutoBinding} tries to keep the target
+         * in sync with the source.
+         */
+        READ,
+
+        /**
+         * An update strategy where the {@code AutoBinding} tries to keep both the
+         * source and target in sync with each other.
+         */
+        READ_WRITE
+    }
+
+    /**
+     * Create an instance of {@code AutoBinding} between two properties of two objects,
+     * with the given update strategy.
+     *
+     * @param strategy the update strategy
+     * @param sourceObject the source object
+     * @param sourceProperty a property on the source object
+     * @param targetObject the target object
+     * @param targetProperty a property on the target object
+     * @param name a name for the {@code Binding}
+     * @throws IllegalArgumentException if the source property or target property is {@code null}
+     */
+    protected AutoBinding(UpdateStrategy strategy, SS sourceObject, Property<SS, SV> sourceProperty, TS targetObject, Property<TS, TV> targetProperty, String name) {
+        super(sourceObject, sourceProperty, targetObject, targetProperty, name);
+
+        if (strategy == null) {
+            throw new IllegalArgumentException("must provide update strategy");
+        }
+
+        this.strategy = strategy;
+    }
+
+    /**
+     * Returns the {@code AutoBinding's} update strategy.
+     *
+     * @return the update strategy
+     */
+    public final UpdateStrategy getUpdateStrategy() {
+        return strategy;
+    }
+
+    private final void tryRefreshThenSave() {
+        SyncFailure refreshFailure = refresh();
+        if (refreshFailure == null) {
+            notifySynced();
+        } else {
+            SyncFailure saveFailure = save();
+            if (saveFailure == null) {
+                notifySynced();
+            } else {
+                notifySyncFailed(refreshFailure);
+            }
+        }
+    }
+
+    private final void trySaveThenRefresh() {
+        SyncFailure saveFailure = save();
+        if (saveFailure == null) {
+            notifySynced();
+        } else if (saveFailure.getType() == SyncFailureType.CONVERSION_FAILED || saveFailure.getType() == SyncFailureType.VALIDATION_FAILED) {
+            notifySyncFailed(saveFailure);
+        } else {
+            SyncFailure refreshFailure = refresh();
+            if (refreshFailure == null) {
+                notifySynced();
+            } else {
+                notifySyncFailed(saveFailure);
+            }
+        }
+    }
+
+    protected void bindImpl() {
+        UpdateStrategy strat = getUpdateStrategy();
+
+        if (strat == UpdateStrategy.READ_ONCE) {
+            refreshAndNotify();
+        } else if (strat == UpdateStrategy.READ) {
+            refreshAndNotify();
+        } else {
+            tryRefreshThenSave();
+        }
+    }
+
+    protected void unbindImpl() {}
+
+    /**
+     * Returns a string representing the internal state of the {@code Binding}.
+     * This method is intended to be used for debugging purposes only,
+     * and the content and format of the returned string may vary between
+     * implementations. The returned string may be empty but may not
+     * be {@code null}.
+     *
+     * @return a string representing the state of the {@code Binding}.
+     */
+    protected String paramString() {
+        return super.paramString() + ", updateStrategy=" + getUpdateStrategy();
+    }
+
+    protected void sourceChangedImpl(PropertyStateEvent pse) {
+        if (strategy == UpdateStrategy.READ_ONCE) {
+            // nothing to do
+        } else if (strategy == UpdateStrategy.READ) {
+            if (pse.getValueChanged()) {
+                refreshAndNotify();
+            }
+        } else if (strategy == UpdateStrategy.READ_WRITE) {
+            if (pse.getValueChanged()) {
+                tryRefreshThenSave();
+            } else if (pse.isWriteable()) {
+                saveAndNotify();
+            }
+        }
+    }
+
+    protected void targetChangedImpl(PropertyStateEvent pse) {
+        if (strategy == UpdateStrategy.READ_ONCE) {
+            // nothing to do
+        } else if (strategy == UpdateStrategy.READ) {
+            if (pse.getWriteableChanged() && pse.isWriteable()) {
+                refreshAndNotify();
+            }
+        } else if (strategy == UpdateStrategy.READ_WRITE) {
+            if (pse.getWriteableChanged() && pse.isWriteable()) {
+                if (pse.getValueChanged()) {
+                    tryRefreshThenSave();
+                } else {
+                    refreshAndNotify();
+                }
+            } else if (pse.getValueChanged()) {
+                trySaveThenRefresh();
+            }
+        }
+    }
+
+}
diff --git a/src/org/jdesktop/beansbinding/BeanProperty.java b/src/org/jdesktop/beansbinding/BeanProperty.java
new file mode 100644
index 0000000..027b703
--- /dev/null
+++ b/src/org/jdesktop/beansbinding/BeanProperty.java
@@ -0,0 +1,1020 @@
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+/*
+ *   TO DO LIST:
+ *
+ *   - Re-think use of PropertyResolutionException.
+ *     Many of the cases should be AssertionErrors, because they shouldn't happen.
+ *     For the others, we should either use an Error subclass to indicate they're
+ *     unrecoverable, or we need to try to leave the object in a consistent state.
+ *     This is very difficult in methods like updateCachedSources where an
+ *     exception can occur at any time while processing the chain.
+ *
+ *   - Do testing with applets/security managers.
+ *
+ *   - Introspector/reflection doesn't work for non-public classes. EL handles this
+ *     by trying to find a version of the method in a public superclass/interface.
+ *     Looking at the code for Introspector (also used by EL), I got the idea that
+ *     it already does something like this. Investigate why EL handles this in an
+ *     extra step, and decide what we need to do in this class.
+ *
+ *   - Add option to turn on validation. For now it's hard-coded to be off.
+ */
+
+package org.jdesktop.beansbinding;
+
+import java.beans.*;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.*;
+import org.jdesktop.observablecollections.ObservableMap;
+import org.jdesktop.observablecollections.ObservableMapListener;
+import static org.jdesktop.beansbinding.PropertyStateEvent.UNREADABLE;
+import org.jdesktop.beansbinding.ext.BeanAdapterFactory;
+
+/**
+ * An implementation of {@code Property} that uses a simple dot-separated path
+ * syntax to address Java Beans properties of source objects. For example, to
+ * create a property representing the {@code firstName} property of an obect:
+ * <p>
+ * <pre><code>
+ *    BeanProperty.create("firstName");
+ *</code></pre>
+ * <p>
+ * Or to create a property representing the {@code firstName} property of
+ * an object's {@code mother} property:
+ * <p>
+ * <pre><code>
+ *    BeanProperty.create("mother.firstName");
+ * </code></pre>
+ * <p>
+ * An instance of {@code BeanProperty} is immutable and can be used with
+ * different source objects. When a {@code PropertyStateListener} is added to
+ * a {@code BeanProperty} for a given source object, the {@code BeanProperty}
+ * starts listening to all objects along the path (based on that source object)
+ * for change notification, and reflects any changes by notifying the
+ * listener associated with the property for that source object. So, in the second
+ * example above, if a {@code PropertyStateListener} is added to the property
+ * for an object {@code Duke}, the {@code PropertyStateListener} is notified
+ * when either {@code Duke's} mother changes (if the new mother's name is
+ * different), or {@code Duke's mother's firstName} changes.
+ * <p>
+ * It is very important that any bean properties addressed via a {@code BeanProperty}
+ * follow the Java Beans specification, including firing property change notification;
+ * otherwise, {@code BeanProperty} cannot respond to change. As some beans outside
+ * of your control may not follow the Java Beans specification, {@code BeanProperty}
+ * always checks the {@link org.jdesktop.beansbinding.ext.BeanAdapterFactory} to
+ * see if a delegate provider has been registered to provide a delegate bean to take
+ * the place of an object for a given property. See the
+ * <a href="ext/package-summary.html">ext package level</a> documentation for more
+ * details.
+ * <p>
+ * When there are no {@code PropertyStateListeners} installed on a {@code BeanProperty}
+ * for a given source, all {@code Property} methods act by traversing the entire
+ * path from the source to the end point, thereby always providing "live" information.
+ * On the contrary, when there are {@code PropertyStateListeners} installed, the beans
+ * along the path (including the final value) are cached, and only updated upon
+ * notification of change from a bean. Again, this makes it very important that any
+ * bean property that could change along the path fires property change notification.
+ * <p>
+ * <a name="READABILITY"><b>Readability</b></a> of a {@code BeanProperty} for a given source is defined as follows:
+ * <i>A {@code BeanProperty} is readable for a given source if and only if
+ * a) each bean in the path, starting with the source, defines a Java Beans getter
+ * method for the the property to be read on it AND b) each bean in the path,
+ * starting with the source and ending with the bean on which we read the final
+ * property, is {@code non-null}. The final value being {@code null} does not
+ * affect the readability.</i>
+ * <p>
+ * So, in the second example given earlier, the {@code BeanProperty} is readable for (@code Duke} when all
+ * of the following are true: {@code Duke} defines a Java Beans getter for
+ * {@code mother}, {@code Duke's mother} defines a Java Beans getter for
+ * {@code firstName}, {@code Duke} is {@code non-null}, {@code Duke's mother}
+ * is {@code non-null}. The {@code BeanProperty} is therefore unreadable when
+ * any of the following is true: {@code Duke} does not define a Java Beans
+ * getter for {@code mother}, {@code Duke's mother} does not define a Java
+ * Beans getter for {@code firstName}, {@code Duke} is {@code null},
+ * {@code Duke's mother} is {@code null}.
+ * <p>
+ * <a name="WRITEABILITY"><b>Writeability</b></a> of a {@code BeanProperty} for a given source is defined as follows:
+ * <i>A {@code BeanProperty} is writeable for a given source if and only if
+ * a) each bean in the path, starting with the source and ending with the bean on
+ * which we set the final property, defines a Java Beans getter method for the
+ * property to be read on it AND b) the bean on which we set the final property
+ * defines a Java Beans setter for the property to be set on it AND c) each bean
+ * in the path, starting with the source and ending with the bean on which we
+ * set the final property, is {@code non-null}. The final value being {@code null}
+ * does not affect the writeability.</i>
+ * <p>
+ * So, in the second example given earlier, the {@code BeanProperty} is writeable for {@code Duke} when all
+ * of the following are true: {@code Duke} defines a Java Beans getter for
+ * {@code mother}, {@code Duke's mother} defines a Java Beans setter for
+ * {@code firstName}, {@code Duke} is {@code non-null}, {@code Duke's mother}
+ * is {@code non-null}. The {@code BeanProperty} is therefore unreadable when
+ * any of the following is true: {@code Duke} does not define a Java Beans
+ * getter for {@code mother}, {@code Duke's mother} does not define a Java
+ * Beans setter for {@code firstName}, {@code Duke} is {@code null},
+ * {@code Duke's mother} is {@code null}.
+ * <p>
+ * In addition to working on Java Beans properties, any object in the path
+ * can be an instance of {@code Map}. In this case, the {@code Map's get}
+ * method is used with the property name as the getter, and the
+ * {@code Map's put} method is used with the property name as the setter.
+ * {@code BeanProperty} can only respond to changes in {@code Maps}
+ * if they are instances of {@link org.jdesktop.observablecollections.ObservableMap}.
+ * <p>
+ * Some methods in this class document that they can throw
+ * {@code PropertyResolutionException} if an exception occurs while trying
+ * to resolve the path. The throwing of this exception represents an abnormal
+ * condition and if listeners are installed for the given source object,
+ * leaves the {@code BeanProperty} in an inconsistent state for that source object.
+ * A {@code BeanProperty} should not be used again for that same source object
+ * after such an exception without first removing all listeners associated with
+ * the {@code BeanProperty} for that source object.
+ *
+ * @param <S> the type of source object that this {@code BeanProperty} operates on
+ * @param <V> the type of value that this {@code BeanProperty} represents
+ *
+ * @author Shannon Hickey
+ * @author Scott Violet
+ */
+public final class BeanProperty<S, V> extends PropertyHelper<S, V> {
+
+    private Property<S, ?> baseProperty;
+    private final PropertyPath path;
+    private IdentityHashMap<S, SourceEntry> map = new IdentityHashMap<S, SourceEntry>();
+    private static final Object NOREAD = new Object();
+
+    private final class SourceEntry implements PropertyChangeListener,
+                                               ObservableMapListener,
+                                               PropertyStateListener {
+
+        private S source;
+        private Object cachedBean;
+        private Object[] cache;
+        private Object cachedValue;
+        private Object cachedWriter;
+        private boolean ignoreChange;
+
+        private SourceEntry(S source) {
+            this.source = source;
+            cache = new Object[path.length()];
+            cache[0] = NOREAD;
+
+            if (baseProperty != null) {
+                baseProperty.addPropertyStateListener(source, this);
+            }
+
+            updateCachedBean();
+            updateCachedSources(0);
+            updateCachedValue();
+            updateCachedWriter();
+        }
+
+        private void cleanup() {
+            for (int i = 0; i < path.length(); i++) {
+                unregisterListener(cache[i], path.get(i), this);
+            }
+
+            if (baseProperty != null) {
+                baseProperty.removePropertyStateListener(source, this);
+            }
+
+            cachedBean = null;
+            cache = null;
+            cachedValue = null;
+            cachedWriter = null;
+        }
+
+        private boolean cachedIsReadable() {
+            return cachedValue != NOREAD;
+        }
+
+        private boolean cachedIsWriteable() {
+            return cachedWriter != null;
+        }
+
+        private int getSourceIndex(Object object) {
+            for (int i = 0; i < cache.length; i++) {
+                if (cache[i] == object) {
+                    return i;
+                }
+            }
+
+            if (object instanceof Map) {
+                return -1;
+            }
+
+            for (int i = 0; i < cache.length; i++) {
+                if (cache[i] != null) {
+                    Object adapter = getAdapter(cache[i], path.get(i));
+                    if (adapter == object) {
+                        return i;
+                    }
+                }
+            }
+
+            return -1;
+        }
+
+        private void updateCachedBean() {
+            cachedBean = getBeanFromSource(source);
+        }
+        
+        private void updateCachedSources(int index) {
+            boolean loggedYet = false;
+            
+            Object src;
+            
+            if (index == 0) {
+                src = cachedBean;
+                
+                if (cache[0] != src) {
+                    unregisterListener(cache[0], path.get(0), this);
+                    
+                    cache[0] = src;
+                    
+                    if (src == null) {
+                        loggedYet = true;
+                        log("updateCachedSources()", "source is null");
+                    } else {
+                        registerListener(src, path.get(0), this);
+                    }
+                }
+                
+                index++;
+            }
+            
+            for (int i = index; i < path.length(); i++) {
+                Object old = cache[i];
+                src = getProperty(cache[i - 1], path.get(i - 1));
+                
+                if (src != old) {
+                    unregisterListener(old, path.get(i), this);
+                    
+                    cache[i] = src;
+                    
+                    if (src == null) {
+                        if (!loggedYet) {
+                            loggedYet = true;
+                            log("updateCachedSources()", "missing source");
+                        }
+                    } else if (src == NOREAD) {
+                        if (!loggedYet) {
+                            loggedYet = true;
+                            log("updateCachedSources()", "missing read method");
+                        }
+                    } else {
+                        registerListener(src, path.get(i), this);
+                    }
+                }
+            }
+        }
+
+        // -1 already used to mean validate all
+        // 0... means something in the path changed
+        private void validateCache(int ignore) {
+
+/* In the future, this debugging code can be enabled via a flag */
+            
+/*
+            for (int i = 0; i < path.length() - 1; i++) {
+                if (i == ignore - 1) {
+                    continue;
+                }
+                
+                Object src = cache[i];
+                
+                if (src == NOREAD) {
+                    return;
+                }
+                
+                Object next = getProperty(src, path.get(i));
+                
+                if (!match(next, cache[i + 1])) {
+                    log("validateCache()", "concurrent modification");
+                }
+            }
+            
+            if (path.length() != ignore) {
+                Object next = getProperty(cache[path.length() - 1], path.getLast());
+                if (!match(cachedValue, next)) {
+                    log("validateCache()", "concurrent modification");
+                }
+                
+                Object src = cache[path.length() - 1];
+                Object writer;
+                if (src == null || src == NOREAD) {
+                    writer = null;
+                } else {
+                    writer = getWriter(cache[path.length() - 1], path.getLast());
+                }
+                
+                if (cachedWriter != writer && (cachedWriter == null || !cachedWriter.equals(writer))) {
+                    log("validateCache()", "concurrent modification");
+                }
+            }
+ */
+        }
+        
+        private void updateCachedWriter() {
+            Object src = cache[path.length() - 1];
+            if (src == null || src == NOREAD) {
+                cachedWriter = null;
+            } else {
+                cachedWriter = getWriter(src, path.getLast());
+                if (cachedWriter == null) {
+                    log("updateCachedWriter()", "missing write method");
+                }
+            }
+        }
+        
+        private void updateCachedValue() {
+            Object src = cache[path.length() - 1];
+            if (src == null || src == NOREAD) {
+                cachedValue = NOREAD;
+            } else {
+                cachedValue = getProperty(cache[path.length() - 1], path.getLast());
+                if (cachedValue == NOREAD) {
+                    log("updateCachedValue()", "missing read method");
+                }
+            }
+        }
+
+        private void bindingPropertyChanged(PropertyStateEvent pse) {
+            validateCache(0);
+            Object oldValue = cachedValue;
+            boolean wasWriteable = cachedIsWriteable();
+            updateCachedBean();
+            updateCachedSources(0);
+            updateCachedValue();
+            updateCachedWriter();
+            notifyListeners(wasWriteable, oldValue, this);
+        }
+        
+        private void cachedValueChanged(int index) {
+            validateCache(index);
+            
+            boolean wasWriteable = cachedIsWriteable();
+            Object oldValue = cachedValue;
+            
+            updateCachedSources(index);
+            updateCachedValue();
+            if (index != path.length()) {
+                updateCachedWriter();
+            }
+            
+            notifyListeners(wasWriteable, oldValue, this);
+        }
+        
+        private void mapValueChanged(ObservableMap map, Object key) {
+            if (ignoreChange) {
+                return;
+            }
+            
+            int index = getSourceIndex(map);
+            
+            if (index == -1) {
+                throw new AssertionError();
+            }
+            
+            if (key.equals(path.get(index))) {
+                cachedValueChanged(index + 1);
+            }
+        }
+
+        public void propertyStateChanged(PropertyStateEvent pe) {
+            if (!pe.getValueChanged()) {
+                return;
+            }
+
+            bindingPropertyChanged(pe);
+        }
+
+        private void propertyValueChanged(PropertyChangeEvent pce) {
+            if (ignoreChange) {
+                return;
+            }
+            
+            int index = getSourceIndex(pce.getSource());
+            
+            if (index == -1) {
+                throw new AssertionError();
+            }
+            
+            String propertyName = pce.getPropertyName();
+            if (propertyName == null || path.get(index).equals(propertyName)) {
+                cachedValueChanged(index + 1);
+            }
+        }
+
+        public void propertyChange(PropertyChangeEvent e) {
+           propertyValueChanged(e);
+        }
+
+        public void mapKeyValueChanged(ObservableMap map, Object key, Object lastValue) {
+            mapValueChanged(map, key);
+        }
+
+        public void mapKeyAdded(ObservableMap map, Object key) {
+            mapValueChanged(map, key);
+        }
+
+        public void mapKeyRemoved(ObservableMap map, Object key, Object value) {
+            mapValueChanged(map, key);
+        }
+    }
+
+    /**
+     * Creates an instance of {@code BeanProperty} for the given path.
+     *
+     * @param path the path
+     * @return an instance of {@code BeanProperty} for the given path
+     * @throws IllegalArgumentException if the path is null, or contains
+     *         no property names
+     */
+    public static final <S, V> BeanProperty<S, V> create(String path) {
+        return new BeanProperty<S, V>(null, path);
+    }
+
+    /**
+     * Creates an instance of {@code BeanProperty} for the given base property
+     * and path. The path is relative to the value of the base property.
+     *
+     * @param baseProperty the base property
+     * @param path the path
+     * @return an instance of {@code BeanProperty} for the given base property and path
+     * @throws IllegalArgumentException if the path is null, or contains
+     *         no property names
+     */
+    public static final <S, V> BeanProperty<S, V> create(Property<S, ?> baseProperty, String path) {
+        return new BeanProperty<S, V>(baseProperty, path);
+    }
+
+    /**
+     * @throws IllegalArgumentException for empty or {@code null} path.
+     */
+    private BeanProperty(Property<S, ?> baseProperty, String path) {
+        this.path = PropertyPath.createPropertyPath(path);
+        this.baseProperty = baseProperty;
+    }
+
+    private Object getLastSource(S source) {
+        Object src = getBeanFromSource(source);
+
+        if (src == null || src == NOREAD) {
+            return src;
+        }
+
+        for (int i = 0; i < path.length() - 1; i++) {
+            src = getProperty(src, path.get(i));
+            if (src == null) {
+                log("getLastSource()", "missing source");
+                return null;
+            }
+            
+            if (src == NOREAD) {
+                log("getLastSource()", "missing read method");
+                return NOREAD;
+            }
+        }
+        
+        return src;
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * See the class level documentation for the definition of <a href="#WRITEABILITY">writeability</a>.
+     *
+     * @throws UnsupportedOperationException {@inheritDoc}
+     * @throws PropertyResolutionException if an exception occurs while resolving the path
+     * @see #setValue
+     * @see #isWriteable
+     */
+    public Class<? extends V> getWriteType(S source) {
+        SourceEntry entry = map.get(source);
+
+        if (entry != null) {
+            entry.validateCache(-1);
+ 
+            if (entry.cachedWriter == null) {
+                throw new UnsupportedOperationException("Unwriteable");
+            }
+ 
+            return (Class<? extends V>)getType(entry.cache[path.length() - 1], path.getLast());
+        }
+
+        return (Class<? extends V>)getType(getLastSource(source), path.getLast());
+    }
+    
+    /**
+     * {@inheritDoc}
+     * <p>
+     * See the class level documentation for the definition of <a href="#READABILITY">readability</a>.
+     *
+     * @throws UnsupportedOperationException {@inheritDoc}
+     * @throws PropertyResolutionException if an exception occurs while resolving the path
+     * @see #isReadable
+     */
+    public V getValue(S source) {
+        SourceEntry entry = map.get(source);
+
+        if (entry != null) {
+            entry.validateCache(-1);
+ 
+            if (entry.cachedValue == NOREAD) {
+                throw new UnsupportedOperationException("Unreadable");
+            }
+ 
+            return (V)entry.cachedValue;
+        }
+        
+        Object src = getLastSource(source);
+        if (src == null || src == NOREAD) {
+            throw new UnsupportedOperationException("Unreadable");
+        }
+        
+        src = getProperty(src, path.getLast());
+        if (src == NOREAD) {
+            log("getValue()", "missing read method");
+            throw new UnsupportedOperationException("Unreadable");
+        }
+        
+        return (V)src;
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * See the class level documentation for the definition of <a href="#WRITEABILITY">writeability</a>.
+     *
+     * @throws UnsupportedOperationException {@inheritDoc}
+     * @throws PropertyResolutionException if an exception occurs while resolving the path
+     * @see #isWriteable
+     * @see #getWriteType
+     */
+    public void setValue(S source, V value) {
+        SourceEntry entry = map.get(source);
+
+        if (entry != null) {
+            entry.validateCache(-1);
+ 
+            if (entry.cachedWriter == null) {
+                throw new UnsupportedOperationException("Unwritable");
+            }
+
+            try {
+                entry.ignoreChange = true;
+                write(entry.cachedWriter, entry.cache[path.length() - 1], path.getLast(), value);
+            } finally {
+                entry.ignoreChange = false;
+            }
+ 
+            Object oldValue = entry.cachedValue;
+            entry.updateCachedValue();
+            notifyListeners(entry.cachedIsWriteable(), oldValue, entry);
+        } else {
+            setProperty(getLastSource(source), path.getLast(), value);
+        }
+    }
+    
+    /**
+     * {@inheritDoc}
+     * <p>
+     * See the class level documentation for the definition of <a href="#READABILITY">readability</a>.
+     *
+     * @throws UnsupportedOperationException {@inheritDoc}
+     * @throws PropertyResolutionException if an exception occurs while resolving the path
+     * @see #isWriteable
+     */
+    public boolean isReadable(S source) {
+        SourceEntry entry = map.get(source);
+
+        if (entry != null) {
+            entry.validateCache(-1);
+            return entry.cachedIsReadable();
+        }
+        
+        Object src = getLastSource(source);
+        if (src == null || src == NOREAD) {
+            return false;
+        }
+        
+        Object reader = getReader(src, path.getLast());
+        if (reader == null) {
+            log("isReadable()", "missing read method");
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * See the class level documentation for the definition of <a href="#WRITEABILITY">writeability</a>.
+     *
+     * @throws UnsupportedOperationException {@inheritDoc}
+     * @throws PropertyResolutionException if an exception occurs while resolving the path
+     * @see #isReadable
+     */
+    public boolean isWriteable(S source) {
+        SourceEntry entry = map.get(source);
+
+        if (entry != null) {
+            entry.validateCache(-1);
+            return entry.cachedIsWriteable();
+        }
+
+        Object src = getLastSource(source);
+        if (src == null || src == NOREAD) {
+            return false;
+        }
+
+        Object writer = getWriter(src, path.getLast());
+        if (writer == null) {
+            log("isWritable()", "missing write method");
+            return false;
+        }
+
+        return true;
+    }
+
+    private Object getBeanFromSource(S source) {
+        if (baseProperty == null) {
+            if (source == null) {
+                log("getBeanFromSource()", "source is null");
+            }
+
+            return source;
+        }
+
+        if (!baseProperty.isReadable(source)) {
+            log("getBeanFromSource()", "unreadable source property");
+            return NOREAD;
+        }
+
+        Object bean = baseProperty.getValue(source);
+        if (bean == null) {
+            log("getBeanFromSource()", "source property returned null");
+            return null;
+        }
+        
+        return bean;
+    }
+
+    protected final void listeningStarted(S source) {
+        SourceEntry entry = map.get(source);
+        if (entry == null) {
+            entry = new SourceEntry(source);
+            map.put(source, entry);
+        }
+    }
+
+    protected final void listeningStopped(S source) {
+        SourceEntry entry = map.remove(source);
+        if (entry != null) {
+            entry.cleanup();
+        }
+    }
+
+    private static boolean didValueChange(Object oldValue, Object newValue) {
+        return oldValue == null || newValue == null || !oldValue.equals(newValue);
+    }
+
+    private void notifyListeners(boolean wasWriteable, Object oldValue, SourceEntry entry) {
+        PropertyStateListener[] listeners = getPropertyStateListeners(entry.source);
+
+        if (listeners == null || listeners.length == 0) {
+            return;
+        }
+
+        oldValue = toUNREADABLE(oldValue);
+        Object newValue = toUNREADABLE(entry.cachedValue);
+        boolean valueChanged = didValueChange(oldValue, newValue);
+        boolean writeableChanged = (wasWriteable != entry.cachedIsWriteable());
+
+        if (!valueChanged && !writeableChanged) {
+            return;
+        }
+
+        PropertyStateEvent pse = new PropertyStateEvent(this,
+                                                        entry.source,
+                                                        valueChanged,
+                                                        oldValue,
+                                                        newValue,
+                                                        writeableChanged,
+                                                        entry.cachedIsWriteable());
+
+        this.firePropertyStateChange(pse);
+    }
+
+    /**
+     * Returns a string representation of the {@code BeanProperty}. This
+     * method is intended to be used for debugging purposes only, and
+     * the content and format of the returned string may vary between
+     * implementations. The returned string may be empty but may not
+     * be {@code null}.
+     *
+     * @return a string representation of this {@code BeanProperty}
+     */
+    public String toString() {
+        return getClass().getName() + "[" + path + "]";
+    }
+
+    /**
+     * @throws PropertyResolutionException
+     */
+    private static BeanInfo getBeanInfo(Object object) {
+        assert object != null;
+
+        try {
+            // PENDING(shannonh) - not sure about the last flag
+            return Introspector.getBeanInfo(object.getClass(), Introspector.IGNORE_ALL_BEANINFO);
+        } catch (IntrospectionException ie) {
+            throw new PropertyResolutionException("Exception while introspecting " + object.getClass().getName(), ie);
+        }
+    }
+
+    /**
+     * @throws PropertyResolutionException
+     */
+    private static PropertyDescriptor getPropertyDescriptor(Object object, String string) {
+        assert object != null;
+
+        PropertyDescriptor[] pds = getBeanInfo(object).getPropertyDescriptors();
+        if (pds == null) {
+            return null;
+        }
+
+        for (PropertyDescriptor pd : pds) {
+            if (!(pd instanceof IndexedPropertyDescriptor) && pd.getName().equals(string)) {
+                return pd;
+            }
+        }
+
+        return null;
+    }
+
+    private static EventSetDescriptor getEventSetDescriptor(Object object) {
+        assert object != null;
+        
+        EventSetDescriptor[] eds = getBeanInfo(object).getEventSetDescriptors();
+        for (EventSetDescriptor ed : eds) {
+            if (ed.getListenerType() == PropertyChangeListener.class) {
+                return ed;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * @throws PropertyResolutionException
+     */
+    private static Object invokeMethod(Method method, Object object, Object... args) {
+        Exception reason = null;
+
+        try {
+            return method.invoke(object, args);
+        } catch (IllegalArgumentException ex) {
+            reason = ex;
+        } catch (IllegalAccessException ex) {
+            reason = ex;
+        } catch (InvocationTargetException ex) {
+            reason = ex;
+        }
+
+        throw new PropertyResolutionException("Exception invoking method " + method + " on " + object, reason);
+    }
+
+    private Object getReader(Object object, String string) {
+        assert object != null;
+
+        if (object instanceof Map) {
+            return object;
+        }
+
+        object = getAdapter(object, string);
+
+        PropertyDescriptor pd = getPropertyDescriptor(object, string);
+        Method readMethod = null;
+        return pd == null ? null : pd.getReadMethod();
+    }
+
+    /**
+     * @throws PropertyResolutionException
+     */
+    private Object read(Object reader, Object object, String string) {
+        assert reader != null;
+
+        if (reader instanceof Map) {
+            assert reader == object;
+            return ((Map)reader).get(string);
+        }
+
+        object = getAdapter(object, string);
+        
+        return invokeMethod((Method)reader, object);
+    }
+
+    /**
+     * @throws PropertyResolutionException
+     */
+    private Object getProperty(Object object, String string) {
+        if (object == null || object == NOREAD) {
+            return NOREAD;
+        }
+
+        Object reader = getReader(object, string);
+        if (reader == null) {
+            return NOREAD;
+        }
+        
+        return read(reader, object, string);
+    }
+
+    /**
+     * @throws PropertyResolutionException
+     */
+    private Class<?> getType(Object object, String string) {
+        if (object == null || object == NOREAD) {
+            throw new UnsupportedOperationException("Unwritable");
+        }
+
+        if (object instanceof Map) {
+            return Object.class;
+        }
+
+        object = getAdapter(object, string);
+        
+        PropertyDescriptor pd = getPropertyDescriptor(object, string);
+        if (pd == null || pd.getWriteMethod() == null) {
+            log("getType()", "missing write method");
+            throw new UnsupportedOperationException("Unwritable");
+        }
+
+        return pd.getPropertyType();
+    }
+
+    private Object getWriter(Object object, String string) {
+        assert object != null;
+
+        if (object instanceof Map) {
+            return object;
+        }
+
+        object = getAdapter(object, string);
+
+        PropertyDescriptor pd = getPropertyDescriptor(object, string);
+        Method writeMethod = null;
+        return pd == null ? null : pd.getWriteMethod();
+    }
+
+    /**
+     * @throws PropertyResolutionException
+     */
+    private void write(Object writer, Object object, String string, Object value) {
+        assert writer != null;
+
+        if (writer instanceof Map) {
+            assert writer == object;
+            ((Map)writer).put(string, value);
+            return;
+        }
+
+        object = getAdapter(object, string);
+        
+        invokeMethod((Method)writer, object, value);
+    }
+
+    /**
+     * @throws PropertyResolutionException
+     * @throws IllegalStateException
+     */
+    private void setProperty(Object object, String string, Object value) {
+        if (object == null || object == NOREAD) {
+            throw new UnsupportedOperationException("Unwritable");
+        }
+
+        Object writer = getWriter(object, string);
+        if (writer == null) {
+            log("setProperty()", "missing write method");
+            throw new UnsupportedOperationException("Unwritable");
+        }
+
+        write(writer, object, string, value);
+    }
+
+    private static Object toUNREADABLE(Object src) {
+        return src == NOREAD ? UNREADABLE : src;
+    }
+
+    private void registerListener(Object object, String property, SourceEntry entry) {
+        assert object != null;
+
+        if (object != NOREAD) {
+            if (object instanceof ObservableMap) {
+                ((ObservableMap)object).addObservableMapListener(entry);
+            } else if (!(object instanceof Map)) {
+                object = getAdapter(object, property);
+                addPropertyChangeListener(object, entry);
+            }
+        }
+    }
+
+    /**
+     * @throws PropertyResolutionException
+     */
+    private void unregisterListener(Object object, String property, SourceEntry entry) {
+        if (object != null && object != NOREAD) {
+            if (object instanceof ObservableMap) {
+                ((ObservableMap)object).removeObservableMapListener(entry);
+            } else if (!(object instanceof Map)) {
+                object = getAdapter(object, property);
+                removePropertyChangeListener(object, entry);
+            }
+        }
+    }
+
+    /**
+     * @throws PropertyResolutionException
+     */
+    private static void addPropertyChangeListener(Object object, PropertyChangeListener listener) {
+        EventSetDescriptor ed = getEventSetDescriptor(object);
+        Method addPCMethod = null;
+
+        if (ed == null || (addPCMethod = ed.getAddListenerMethod()) == null) {
+            log("addPropertyChangeListener()", "can't add listener");
+            return;
+        }
+
+        invokeMethod(addPCMethod, object, listener);
+    }
+
+    /**
+     * @throws PropertyResolutionException
+     */
+    private static void removePropertyChangeListener(Object object, PropertyChangeListener listener) {
+        EventSetDescriptor ed = getEventSetDescriptor(object);
+        Method removePCMethod = null;
+
+        if (ed == null || (removePCMethod = ed.getRemoveListenerMethod()) == null) {
+            log("removePropertyChangeListener()", "can't remove listener from source");
+            return;
+        }
+        
+        invokeMethod(removePCMethod, object, listener);
+    }
+
+    private static boolean wrapsLiteral(Object o) {
+        assert o != null;
+
+        return o instanceof String ||
+               o instanceof Byte ||
+               o instanceof Character ||
+               o instanceof Boolean ||
+               o instanceof Short ||
+               o instanceof Integer ||
+               o instanceof Long ||
+               o instanceof Float ||
+               o instanceof Double;
+    }
+
+    // need special match method because when using reflection
+    // to get a primitive value, the value is always wrapped in
+    // a new object
+    private static boolean match(Object a, Object b) {
+        if (a == b) {
+            return true;
+        }
+
+        if (a == null) {
+            return false;
+        }
+
+        if (wrapsLiteral(a)) {
+            return a.equals(b);
+        }
+
+        return false;
+    }
+
+    private Object getAdapter(Object o, String property) {
+        Object adapter = null;
+        adapter = BeanAdapterFactory.getAdapter(o, property);
+        return adapter == null ? o : adapter;
+    }
+    
+    private static final boolean LOG = false;
+
+    private static void log(String method, String message) {
+        if (LOG) {
+            System.err.println("LOG: " + method + ": " + message);
+        }
+    }
+    
+}
diff --git a/src/org/jdesktop/beansbinding/Binding.java b/src/org/jdesktop/beansbinding/Binding.java
new file mode 100644
index 0000000..a375725
--- /dev/null
+++ b/src/org/jdesktop/beansbinding/Binding.java
@@ -0,0 +1,1625 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.beansbinding;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.beans.*;
+
+/**
+ * {@code Binding} is an abstract class that represents the concept of a
+ * binding between two properties, typically of two objects, and contains
+ * methods for explicitly syncing the values of the two properties. {@code Binding}
+ * itself does no automatic syncing between property values. Subclasses
+ * will typically keep the values in sync according to some strategy.
+ * <p>
+ * Some {@code Bindings} are managed, often by another {@code Binding}.
+ * A managed {@code Binding} does not allow certain methods to be called by
+ * the user. These methods are identified in their documentation.
+ * Subclasses should call {@code setManaged(true)} to make themselves managed.
+ * {@code Binding} provides protected versions of the managed methods with the
+ * suffix {@code "Unmanaged"} for subclasses to use internally without
+ * checking whether or not they are managed.
+ * <p>
+ * Any {@code PropertyResolutionExceptions} thrown by {@code Property}
+ * objects used by this binding are allowed to flow through to the caller
+ * of the {@code Binding} methods.
+ *
+ * @param <SS> the type of source object
+ * @param <SV> the type of value that the source property represents
+ * @param <TS> the type of target object
+ * @param <TV> the type of value that the target property represents
+ *
+ * @author Shannon Hickey
+ */
+public abstract class Binding<SS, SV, TS, TV> {
+
+    private String name;
+    private SS sourceObject;
+    private TS targetObject;
+    private Property<SS, SV> sourceProperty;
+    private Property<TS, TV> targetProperty;
+    private Validator<? super SV> validator;
+    private Converter<SV, TV> converter;
+    private TV sourceNullValue;
+    private SV targetNullValue;
+    private TV sourceUnreadableValue;
+    private boolean sourceUnreadableValueSet;
+    private List<BindingListener> listeners;
+    private PropertyStateListener psl;
+    private boolean ignoreChange;
+    private boolean isManaged;
+    private boolean isBound;
+    private PropertyChangeSupport changeSupport;
+
+    /**
+     * An enumeration representing the reasons a sync ({@code save} or {@code refresh})
+     * can fail on a {@code Binding}.
+     *
+     * @see Binding#refresh
+     * @see Binding#save
+     */
+    public enum SyncFailureType {
+        
+        /**
+         * A {@code refresh} failed because the {@code Binding's} target property is unwriteable
+         * for the {@code Binding's} target object.
+         */
+        TARGET_UNWRITEABLE,
+        
+        /**
+         * A {@code save} failed because the {@code Binding's} source property is unwriteable
+         * for the {@code Binding's} source object.
+         */
+        SOURCE_UNWRITEABLE,
+        
+        /**
+         * A {@code save} failed because the {@code Binding's} target property is unreadable
+         * for the {@code Binding's} target object.
+         */
+        TARGET_UNREADABLE,
+
+        /**
+         * A {@code refresh} failed because the {@code Binding's} source property is unreadable
+         * for the {@code Binding's} source object.
+         */
+        SOURCE_UNREADABLE,
+        
+        /**
+         * A {@code save} failed due to a conversion failure on the value
+         * returned by the {@code Binding's} target property for the {@code Binding's}
+         * target object.
+         */
+        CONVERSION_FAILED,
+        
+        /**
+         * A {@code save} failed due to a validation failure on the value
+         * returned by the {@code Binding's} target property for the {@code Binding's}
+         * target object.
+         */
+        VALIDATION_FAILED
+    }
+
+    /**
+     * {@code SyncFailure} represents a failure to sync ({@code save} or {@code refresh}) a
+     * {@code Binding}.
+     */
+    public static final class SyncFailure {
+        private SyncFailureType type;
+        private Object reason;
+
+        private static SyncFailure TARGET_UNWRITEABLE = new SyncFailure(SyncFailureType.TARGET_UNWRITEABLE);
+        private static SyncFailure SOURCE_UNWRITEABLE = new SyncFailure(SyncFailureType.SOURCE_UNWRITEABLE);
+        private static SyncFailure TARGET_UNREADABLE = new SyncFailure(SyncFailureType.TARGET_UNREADABLE);
+        private static SyncFailure SOURCE_UNREADABLE = new SyncFailure(SyncFailureType.SOURCE_UNREADABLE);
+
+        private static SyncFailure conversionFailure(RuntimeException rte) {
+            return new SyncFailure(rte);
+        }
+
+        private static SyncFailure validationFailure(Validator.Result result) {
+            return new SyncFailure(result);
+        }
+
+        private SyncFailure(SyncFailureType type) {
+            if (type == SyncFailureType.CONVERSION_FAILED || type == SyncFailureType.VALIDATION_FAILED) {
+                throw new IllegalArgumentException();
+            }
+
+            this.type = type;
+        }
+
+        private SyncFailure(RuntimeException exception) {
+            this.type = SyncFailureType.CONVERSION_FAILED;
+            this.reason = exception;
+        }
+
+        private SyncFailure(Validator.Result result) {
+            this.type = SyncFailureType.VALIDATION_FAILED;
+            this.reason = result;
+        }
+
+        /**
+         * Returns the type of failure.
+         *
+         * @return the type of failure
+         */
+        public SyncFailureType getType() {
+            return type;
+        }
+
+        /**
+         * Returns the exception that occurred during conversion if
+         * this failure represents a conversion failure. Throws
+         * {@code UnsupportedOperationException} otherwise.
+         *
+         * @return the exception that occurred during conversion
+         * @throws UnsupportedOperationException if the type of failure
+         *         is not {@code SyncFailureType.CONVERSION_FAILED}
+         */
+        public RuntimeException getConversionException() {
+            if (type != SyncFailureType.CONVERSION_FAILED) {
+                throw new UnsupportedOperationException();
+            }
+            
+            return (RuntimeException)reason;
+        }
+
+        /**
+         * Returns the result that was returned from the
+         * {@code Binding's} validator if this failure represents a
+         * validation failure. Throws {@code UnsupportedOperationException} otherwise.
+         *
+         * @return the result that was returned from the {@code Binding's} validator
+         * @throws UnsupportedOperationException if the type of failure
+         *         is not {@code SyncFailureType.VALIDATION_FAILED}
+         */
+        public Validator.Result getValidationResult() {
+            if (type != SyncFailureType.VALIDATION_FAILED) {
+                throw new UnsupportedOperationException();
+            }
+            
+            return (Validator.Result)reason;
+        }
+
+        /**
+         * Returns a string representation of the {@code SyncFailure}. This
+         * method is intended to be used for debugging purposes only, and
+         * the content and format of the returned string may vary between
+         * implementations. The returned string may be empty but may not
+         * be {@code null}.
+         *
+         * @return a string representation of this {@code SyncFailure}
+         */
+        public String toString() {
+            return type + (reason == null ? "" : ": " + reason.toString());
+        }
+    }
+
+    /**
+     * Encapsulates the result from calling
+     * {@link org.jdesktop.beansbinding.Binding#getSourceValueForTarget} or
+     * {@link org.jdesktop.beansbinding.Binding#getTargetValueForSource}, which
+     * can either be a successful value or a failure.
+     */
+    public static final class ValueResult<V> {
+        private V value;
+        private SyncFailure failure;
+
+        private ValueResult(V value) {
+            this.value = value;
+        }
+
+        private ValueResult(SyncFailure failure) {
+            if (failure == null) {
+                throw new AssertionError();
+            }
+
+            this.failure = failure;
+        }
+
+        /**
+         * Returns {@code true} if this {@code ValueResult} represents
+         * a failure and {@code false} otherwise.
+         *
+         * @return {@code true} if this {@code ValueResult} represents
+         *         a failure and {@code false} otherwise
+         * @see #getFailure
+         */
+        public boolean failed() {
+            return failure != null;
+        }
+
+        /**
+         * Returns the resulting value if this {@code ValueResult} does
+         * not represent a failure and throws {@code UnsupportedOperationException}
+         * otherwise.
+         *
+         * @return the resulting value
+         * @throws UnsupportedOperationException if this {@code ValueResult} represents a failure
+         * @see #failed
+         */
+        public V getValue() {
+            if (failed()) {
+                throw new UnsupportedOperationException();
+            }
+
+            return value;
+        }
+
+        /**
+         * Returns the failure if this {@code ValueResult} represents
+         * a failure and throws {@code UnsupportedOperationException}
+         * otherwise.
+         *
+         * @return the failure
+         * @throws UnsupportedOperationException if this {@code ValueResult} does not represent a failure
+         * @see #failed
+         */
+        public SyncFailure getFailure() {
+            if (!failed()) {
+                throw new UnsupportedOperationException();
+            }
+            
+            return failure;
+        }
+
+        /**
+         * Returns a string representation of the {@code ValueResult}. This
+         * method is intended to be used for debugging purposes only, and
+         * the content and format of the returned string may vary between
+         * implementations. The returned string may be empty but may not
+         * be {@code null}.
+         *
+         * @return a string representation of this {@code ValueResult}
+         */
+        public String toString() {
+            return value == null ? "failure: " + failure : "value: " + value;
+        }
+    }
+
+    /**
+     * Create an instance of {@code Binding} between two properties of two objects.
+     *
+     * @param sourceObject the source object
+     * @param sourceProperty a property on the source object
+     * @param targetObject the target object
+     * @param targetProperty a property on the target object
+     * @param name a name for the {@code Binding}
+     * @throws IllegalArgumentException if the source property or target property is {@code null}
+     */
+    protected Binding(SS sourceObject, Property<SS, SV> sourceProperty, TS targetObject, Property<TS, TV> targetProperty, String name) {
+        setSourceProperty(sourceProperty);
+        setTargetProperty(targetProperty);
+
+        this.sourceObject = sourceObject;
+        this.sourceProperty = sourceProperty;
+        this.targetObject = targetObject;
+        this.targetProperty = targetProperty;
+        this.name = name;
+    }
+
+    /**
+     * Sets the {@code Binding's} source property.
+     * <p>
+     * {@code Binding} fires a property change notification with
+     * property name {@code "sourceProperty"} when the value of
+     * this property changes.
+     * <p>
+     * This method may not be called on a bound binding.
+     *
+     * @param sourceProperty the source property
+     * @throws IllegalArgumentException if the source property is {@code null}
+     * @throws IllegalStateException if the {@code Binding} is bound
+     * @see #isBound()
+     */
+    protected final void setSourceProperty(Property<SS, SV> sourceProperty) {
+        throwIfBound();
+        if (sourceProperty == null) {
+            throw new IllegalArgumentException("source property can't be null");
+        }
+        Property<SS, SV> old = this.sourceProperty;
+        this.sourceProperty = sourceProperty;
+        firePropertyChange("sourceProperty", old, sourceProperty);
+    }
+    
+    /**
+     * Sets the {@code Binding's} target property.
+     * <p>
+     * {@code Binding} fires a property change notification with
+     * property name {@code "targetProperty"} when the value of
+     * this property changes.
+     * <p>
+     * This method may not be called on a bound binding.
+     *
+     * @param targetProperty the target property
+     * @throws IllegalArgumentException if the target property is {@code null}
+     * @throws IllegalStateException if the {@code Binding} is bound
+     * @see #isBound()
+     */
+    protected final void setTargetProperty(Property<TS, TV> targetProperty) {
+        throwIfBound();
+        if (targetProperty == null) {
+            throw new IllegalArgumentException("target property can't be null");
+        }
+        Property<TS, TV> old = this.targetProperty;
+        this.targetProperty = targetProperty;
+        firePropertyChange("targetProperty", old, targetProperty);
+    }
+    
+    /**
+     * Returns the {@code Binding's} name, which may be {@code null}.
+     *
+     * @return the {@code Binding's} name, or {@code null}
+     */
+    public final String getName() {
+        return name;
+    }
+
+    /**
+     * Returns the {@code Binding's} source property, which may not be {@code null}.
+     *
+     * @return the {@code Binding's} source property, {@code non-null}
+     * @see #setSourceProperty
+     */
+    public final Property<SS, SV> getSourceProperty() {
+        return sourceProperty;
+    }
+
+    /**
+     * Returns the {@code Binding's} target property, which may not be {@code null}.
+     *
+     * @return the {@code Binding's} target property, {@code non-null}
+     * @see #setTargetProperty
+     */
+    public final Property<TS, TV> getTargetProperty() {
+        return targetProperty;
+    }
+
+    /**
+     * Returns the {@code Binding's} source object, which may be {@code null}.
+     *
+     * @return the {@code Binding's} source object, or {@code null}
+     * @see #setSourceObject
+     */
+    public final SS getSourceObject() {
+        return sourceObject;
+    }
+
+    /**
+     * Returns the {@code Binding's} target object, which may be {@code null}.
+     *
+     * @return the {@code Binding's} target object, or {@code null}
+     * @see #setTargetObject
+     */
+    public final TS getTargetObject() {
+        return targetObject;
+    }
+
+    /**
+     * Sets the {@code Binding's} source object, which may be {@code null}.
+     * <p>
+     * {@code Binding} fires a property change notification with
+     * property name {@code "sourceObject"} when the value of
+     * this property changes.
+     * <p>
+     * This method may not be called on a managed or bound binding.
+     *
+     * @param sourceObject the source object, or {@code null}
+     * @throws UnsupportedOperationException if the {@code Binding} is managed
+     * @throws IllegalStateException if the {@code Binding} is bound
+     * @see #isManaged()
+     * @see #isBound()
+     */
+    public final void setSourceObject(SS sourceObject) {
+        throwIfManaged();
+        setSourceObjectUnmanaged(sourceObject);
+    }
+
+    /**
+     * A protected version of {@link #setSourceObject} that allows managed
+     * subclasses to set the source object without throwing an exception
+     * for being managed.
+     *
+     * @param sourceObject the source object, or {@code null}
+     * @throws IllegalStateException if the {@code Binding} is bound
+     * @see #isManaged()
+     * @see #isBound()
+     */
+    protected final void setSourceObjectUnmanaged(SS sourceObject) {
+        throwIfBound();
+        SS old = this.sourceObject;
+        this.sourceObject = sourceObject;
+        firePropertyChange("sourceObject", old, sourceObject);
+    }
+
+    /**
+     * Sets the {@code Binding's} target object, which may be {@code null}.
+     * <p>
+     * {@code Binding} fires a property change notification with
+     * property name {@code "targetObject"} when the value of
+     * this property changes.
+     * <p>
+     * This method may not be called on a managed or bound binding.
+     *
+     * @param targetObject the target object, or {@code null}
+     * @throws UnsupportedOperationException if the {@code Binding} is managed
+     * @throws IllegalStateException if the {@code Binding} is bound
+     * @see #isManaged()
+     * @see #isBound()
+     */
+    public final void setTargetObject(TS targetObject) {
+        throwIfManaged();
+        setTargetObjectUnmanaged(targetObject);
+    }
+
+    /**
+     * A protected version of {@link #setTargetObject} that allows managed
+     * subclasses to set the target object without throwing an exception
+     * for being managed.
+     *
+     * @param targetObject the target object, or {@code null}
+     * @throws IllegalStateException if the {@code Binding} is bound
+     * @see #isManaged()
+     * @see #isBound()
+     */
+    protected final void setTargetObjectUnmanaged(TS targetObject) {
+        throwIfBound();
+        TS old = this.targetObject;
+        this.targetObject = targetObject;
+        firePropertyChange("targetObject", old, targetObject);
+    }
+
+    /**
+     * Sets the {@code Validator} for the {@code Binding}, which may be {@code null}.
+     * <p>
+     * {@code Binding} fires a property change notification with
+     * property name {@code "validator"} when the value of
+     * this property changes.
+     * <p>
+     * This method may not be called on a bound binding.
+     * <p>
+     * See the documentation on {@link #getTargetValueForSource} for details on how
+     * a {@code Binding's Validator} is used.
+     *
+     * @param validator the {@code Validator}, or {@code null}
+     * @throws IllegalStateException if the {@code Binding} is bound
+     * @see #isBound()
+     */
+    public final void setValidator(Validator<? super SV> validator) {
+        throwIfBound();
+        Validator<? super SV> old = this.validator;
+        this.validator = validator;
+        firePropertyChange("validator", old, validator);
+    }
+
+    /**
+     * Returns the {@code Binding's Validator}, which may be {@code null}.
+     *
+     * @return the {@code Binding's Validator}, or {@code null}
+     * @see #setValidator
+     */
+    public final Validator<? super SV> getValidator() {
+        return validator;
+    }
+
+    /**
+     * Sets the {@code Converter} for the {@code Binding}, which may be {@code null}.
+     * <p>
+     * {@code Binding} fires a property change notification with
+     * property name {@code "converter"} when the value of
+     * this property changes.
+     * <p>
+     * This method may not be called on a bound binding.
+     * <p>
+     * See the documentation on {@link #getTargetValueForSource} and
+     * {@link #getSourceValueForTarget} for details on how
+     * a {@code Binding's Converter} is used.
+     *
+     * @param converter the {@code Converter}, or {@code null}
+     * @throws IllegalStateException if the {@code Binding} is bound
+     * @see #isBound()
+     */
+    public final void setConverter(Converter<SV, TV> converter) {
+        throwIfBound();
+        Converter<SV, TV> old = this.converter;
+        this.converter = converter;
+        firePropertyChange("converter", old, converter);
+    }
+
+    /**
+     * Returns the {@code Binding's Converter}, which may be {@code null}.
+     *
+     * @return the {@code Binding's Converter}, or {@code null}
+     * @see #setConverter
+     */
+    public final Converter<SV, TV> getConverter() {
+        return converter;
+    }
+
+    /**
+     * Sets the value to be returned by {@link #getSourceValueForTarget}
+     * when the source property returns {@code null} for the source object.
+     * The default for this property is {@code null}.
+     * <p>
+     * {@code Binding} fires a property change notification with
+     * property name {@code "sourceNullValue"} when the value of
+     * this property changes.
+     * <p>
+     * This method may not be called on a bound binding.
+     *
+     * @param sourceNullValue the value, or {@code null}
+     * @throws IllegalStateException if the {@code Binding} is bound
+     */
+    public final void setSourceNullValue(TV sourceNullValue) {
+        throwIfBound();
+        TV old = this.sourceNullValue;
+        this.sourceNullValue = sourceNullValue;
+        firePropertyChange("sourceNullValue", old, sourceNullValue);
+    }
+
+    /**
+     * Returns the value to be returned by {@link #getSourceValueForTarget}
+     * when the source property returns {@code null} for the source object.
+     * The default for this property is {@code null}.
+     *
+     * @return the value that replaces a source value of {@code null}, or {@code null}
+     *         if there is no replacement
+     * @see #setSourceNullValue
+     */
+    public final TV getSourceNullValue() {
+        return sourceNullValue;
+    }
+
+    /**
+     * Sets the value to be returned by {@link #getTargetValueForSource}
+     * when the target property returns {@code null} for the target object.
+     * The default for this property is {@code null}.
+     * <p>
+     * {@code Binding} fires a property change notification with
+     * property name {@code "targetNullValue"} when the value of
+     * this property changes.
+     * <p>
+     * This method may not be called on a bound binding.
+     *
+     * @param targetNullValue the value, or {@code null}
+     * @throws IllegalStateException if the {@code Binding} is bound
+     */
+    public final void setTargetNullValue(SV targetNullValue) {
+        throwIfBound();
+        SV old = this.targetNullValue;
+        this.targetNullValue = targetNullValue;
+        firePropertyChange("targetNullValue", old, targetNullValue);
+    }
+
+    /**
+     * Returns the value to be returned by {@link #getTargetValueForSource}
+     * when the target property returns {@code null} for the target object.
+     * The default for this property is {@code null}.
+     *
+     * @return the value that replaces a target value of {@code null}, or {@code null}
+     *         if there is no replacement
+     * @see #setTargetNullValue
+     */
+    public final SV getTargetNullValue() {
+        return targetNullValue;
+    }
+
+    /**
+     * Sets the value to be returned by {@link #getSourceValueForTarget}
+     * when the source property is unreadable for the source object.
+     * Calling this method stores the given value and indicates that
+     * {@code getSourceValueForTarget} should use it, by setting the
+     * {@code sourceUnreadableValueSet} property to {@code true}.
+     * <p>
+     * By default, the {@code sourceUnreadableValue} property is unset,
+     * indicated by the {@code sourceUnreadableValueSet} property being
+     * {@code false}.
+     * <p>
+     * Setting this property to {@code null} acts the same as setting it to
+     * any other value. To return the property to the unset state (clearing
+     * the value and setting {@code sourceUnreadableValueSet} back to
+     * {@code false}) call {@link #unsetSourceUnreadableValue}.
+     * <p>
+     * If this property was previously unset, this method fires a property
+     * change notification with property name {@code "sourceUnreadableValueSet"}.
+     * For all invocations, it also fires a property change notification with
+     * property name {@code "sourceUnreadableValue"}, if necessary, to indicate
+     * a change in the property value. If previously unset, the event will
+     * indicate an old value of {@code null}.
+     * <p>
+     * This method may not be called on a bound binding.
+     *
+     * @param sourceUnreadableValue the value, which may be {@code null}
+     * @throws IllegalStateException if the {@code Binding} is bound
+     * @see #isSourceUnreadableValueSet
+     * @see #getSourceUnreadableValue
+     */
+    public final void setSourceUnreadableValue(TV sourceUnreadableValue) {
+        throwIfBound();
+
+        TV old = this.sourceUnreadableValue;
+        boolean oldSet = this.sourceUnreadableValueSet;
+
+        this.sourceUnreadableValue = sourceUnreadableValue;
+        this.sourceUnreadableValueSet = true;
+
+        firePropertyChange("sourceUnreadableValueSet", oldSet, true);
+        firePropertyChange("sourceUnreadableValue", old, sourceUnreadableValue);
+    }
+
+    /**
+     * Unsets the value of the {@code sourceUnreadableValue} property by clearing
+     * the value and setting the value of the {@code sourceUnreadableValueSet}
+     * property to {@code false}.
+     * <p>
+     * If the property was previously set, fires a property change notification
+     * with property name {@code "sourceUnreadableValueSet"}, and a property
+     * change notification with property name {@code "sourceUnreadableValue"}.
+     * The event for the latter notification will have a new value of {@code null}.
+     * <p>
+     * See the documentation for {@link #setSourceUnreadableValue} for more
+     * information on the {@code sourceUnreadableValue} property.
+     * <p>
+     * This method may not be called on a bound binding.
+     *
+     * @throws IllegalStateException if the {@code Binding} is bound
+     * @see #isSourceUnreadableValueSet
+     * @see #getSourceUnreadableValue
+     */
+    public final void unsetSourceUnreadableValue() {
+        throwIfBound();
+
+        if (isSourceUnreadableValueSet()) {
+            TV old = this.sourceUnreadableValue;
+            
+            this.sourceUnreadableValue = null;
+            this.sourceUnreadableValueSet = false;
+
+            firePropertyChange("sourceUnreadableValueSet", true, false);
+            firePropertyChange("sourceUnreadableValue", old, null);
+        }
+
+    }
+
+    /**
+     * Returns the value of the {@code sourceUnreadableValueSet} property,
+     * which indicates whether or not the {@code sourceUnreadableValue} property
+     * is set on the {@code Binding}.
+     * <p>
+     * See the documentation for {@link #setSourceUnreadableValue} for more
+     * information on the {@code sourceUnreadableValue} property.
+     *
+     * @return whether or not the {@code sourceUnreadableValue} property
+     *         is set on the {@code Binding}
+     * @see #unsetSourceUnreadableValue
+     * @see #getSourceUnreadableValue
+     */
+    public final boolean isSourceUnreadableValueSet() {
+        return sourceUnreadableValueSet;
+    }
+
+    /**
+     * If set, returns the value to be returned by {@link #getSourceValueForTarget}
+     * when the source property is unreadable for the source object. Throws
+     * {@code UnsupportedOperationException} if the property is not set,
+     * as indicated by {@link #isSourceUnreadableValueSet}.
+     * <p>
+     * See the documentation for {@link #setSourceUnreadableValue} for more
+     * information on this property.
+     *
+     * @return the value that replaces an unreadable source value, which may
+     *         be {@code null}
+     * @see #unsetSourceUnreadableValue
+     * @throws UnsupportedOperationException if the property is not set,
+     *         as indicated by {@code isSourceUnreadableValueSet}
+     */
+    public final TV getSourceUnreadableValue() {
+        if (!isSourceUnreadableValueSet()) {
+            throw new UnsupportedOperationException("not set");
+        }
+
+        return sourceUnreadableValue;
+    }
+
+    /**
+     * Adds a {@code BindingListener} to be notified of changes to this {@code Binding}.
+     * Does nothing if the listener is {@code null}. If a listener is added more than once,
+     * notifications are sent to that listener once for every time that it has
+     * been added. The ordering of listener notification is unspecified.
+     *
+     * @param listener the listener to add
+     */
+    public final void addBindingListener(BindingListener listener) {
+        if (listener == null) {
+            return;
+        }
+
+        if (listeners == null) {
+            listeners = new ArrayList<BindingListener>();
+        }
+
+        listeners.add(listener);
+    }
+
+    /**
+     * Removes a {@code BindingListener} from the {@code Binding}. Does
+     * nothing if the listener is {@code null} or is not one of those registered.
+     * If the listener being removed was registered more than once, only one
+     * occurrence of the listener is removed from the list of listeners.
+     * The ordering of listener notification is unspecified.
+     *
+     * @param listener the listener to remove
+     * @see #addBindingListener
+     */
+    public final void removeBindingListener(BindingListener listener) {
+        if (listener == null) {
+            return;
+        }
+
+        if (listeners != null) {
+            listeners.remove(listener);
+        }
+    }
+
+    /**
+     * Returns the list of {@code BindingListeners} registered on this
+     * {@code Binding}. Order is undefined. Returns an empty array if there are
+     * no listeners.
+     *
+     * @return the list of {@code BindingListeners} registered on this {@code Binding}
+     * @see #addBindingListener
+     */
+    public final BindingListener[] getBindingListeners() {
+        if (listeners == null) {
+            return new BindingListener[0];
+        }
+
+        BindingListener[] ret = new BindingListener[listeners.size()];
+        ret = listeners.toArray(ret);
+        return ret;
+    }
+
+    /**
+     * Fetches the value of the source property for the source object and
+     * returns a {@code ValueResult} representing that value in terms that
+     * can be set on the target property for the target object.
+     * <p>
+     * First, if the target property is not writeable for the target object,
+     * a {@code ValueResult} is returned representing a failure
+     * with failure type {@code SyncFailureType.TARGET_UNWRITEABLE}.
+     * Then, if the source property is unreadable for the source object,
+     * the value of {@link #isSourceUnreadableValueSet} is checked. If {@code true}
+     * then a {@code ValueResult} is returned containing the value of the
+     * {@code Binding's} {@link #getSourceUnreadableValue}. Otherwise a
+     * {@code ValueResult} is returned representing a failure with failure
+     * type {@code SyncFailureType.SOURCE_UNREADABLE}.
+     * <p>
+     * Next, the value of the source property is fetched for the source
+     * object. If the value is {@code null}, a {@code ValueResult} is
+     * returned containing the value of the {@code Binding's}
+     * {@link #getSourceNullValue}. If the value is {@code non-null},
+     * the {@code Binding's Converter}, if any, is run to convert
+     * the value from source type to the target property's
+     * {@code getWriteType}, by calling its {@code convertForward}
+     * method with the value. If no {@code Converter} is registered,
+     * a set of default converters is checked to see if one of them
+     * can convert the value to the target type. Finally, the value
+     * (converted or not) is cast to the target write type.
+     * <p>
+     * This final value is returned in a {@code ValueResult}.
+     * <p>
+     * Any {@code RuntimeException} or {@code ClassCastException} thrown by a
+     * converter or the final cast is propogated up to the caller of this method.
+     *
+     * @return a {@code ValueResult} as described above
+     * @throws RuntimeException if thrown by any of the converters
+     * @throws ClassCastException if thrown by a converter or the final cast
+     */
+    public final ValueResult<TV> getSourceValueForTarget() {
+        if (!targetProperty.isWriteable(targetObject)) {
+            return new ValueResult<TV>(SyncFailure.TARGET_UNWRITEABLE);
+        }
+
+        if (!sourceProperty.isReadable(sourceObject)) {
+            if (sourceUnreadableValueSet) {
+                return new ValueResult<TV>(sourceUnreadableValue);
+            } else {
+                return new ValueResult<TV>(SyncFailure.SOURCE_UNREADABLE);
+            }
+        }
+
+        TV value;
+
+        SV rawValue = sourceProperty.getValue(sourceObject);
+
+        if (rawValue == null) {
+            value = sourceNullValue;
+        } else {
+            // may throw ClassCastException or other RuntimeException here;
+            // allow it to be propogated back to the user of Binding
+            value = convertForward(rawValue);
+        }
+
+        return new ValueResult<TV>(value);
+    }
+
+    /**
+     * Fetches the value of the target property for the target object and
+     * returns a {@code ValueResult} representing that value in terms that
+     * can be set on the source property for the source object.
+     * <p>
+     * First, if the source property is not writeable for the source object,
+     * a {@code ValueResult} is returned representing a failure
+     * with failure type {@code SyncFailureType.SOURCE_UNWRITEABLE}.
+     * Then, if the target property is not readable for the target object,
+     * a {@code ValueResult} is returned representing a failure
+     * with failure type {@code SyncFailureType.TARGET_UNREADABLE}.
+     * <p>
+     * Next, the value of the target property is fetched for the target
+     * object. If the value is {@code null}, a {@code ValueResult} is
+     * returned containing the value of the {@code Binding's}
+     * {@link #getTargetNullValue}. If the value is {@code non-null},
+     * the {@code Binding's Converter}, if any, is run to convert
+     * the value from target type to the source property's
+     * {@code getWriteType}, by calling its {@code convertReverse}
+     * method with the value. If no {@code Converter} is registered,
+     * a set of default converters is checked to see if one of them
+     * can convert the value to the source type. Finally, the value
+     * (converted or not) is cast to the source write type.
+     * <p>
+     * If a converter throws a {@code RuntimeException} other than
+     * {@code ClassCastException}, this method returns a
+     * {@code ValueResult} containing the failure, with failure type
+     * {@code SyncFailureType.CONVERSION_FAILURE}.
+     * <p>
+     * As the last step, the {@code Binding's Validator}, if any, is called
+     * upon to validate the final value. If the {@code Validator}
+     * returns {@code non-null} from its {@code validate} method,
+     * a {@code ValueResult} is returned containing the validation
+     * result, with failure type {@code SyncFailureType.VALIDATION_FAILURE}.
+     * Otherwise a {@code ValueResult} is returned containing the
+     * final validated value.
+     * <p>
+     * Any {@code ClassCastException} thrown by a converter or the final
+     * cast is propogated up to the caller of this method.
+     *
+     * @return a {@code ValueResult} as described above
+     * @throws ClassCastException if thrown by a converter or the final cast
+     */
+    public final ValueResult<SV> getTargetValueForSource() {
+        if (!sourceProperty.isWriteable(sourceObject)) {
+            return new ValueResult<SV>(SyncFailure.SOURCE_UNWRITEABLE);
+        }
+
+        if (!targetProperty.isReadable(targetObject)) {
+            return new ValueResult<SV>(SyncFailure.TARGET_UNREADABLE);
+        }
+
+        SV value = null;
+        TV rawValue = targetProperty.getValue(targetObject);
+
+        if (rawValue == null) {
+            value = targetNullValue;
+        } else {
+            try {
+                value = convertReverse(rawValue);
+            } catch (ClassCastException cce) {
+                throw cce;
+            } catch (RuntimeException rte) {
+                return new ValueResult<SV>(SyncFailure.conversionFailure(rte));
+            }
+
+            if (validator != null) {
+                Validator.Result vr = validator.validate(value);
+                if (vr != null) {
+                    return new ValueResult<SV>(SyncFailure.validationFailure(vr));
+                }
+            }
+        }
+
+        return new ValueResult<SV>((SV)value);
+    }
+
+    /**
+     * Binds this binding. Calls {@link #bindImpl} to allow subclasses
+     * to initiate binding, adds a {@code PropertyStateListener} to the source
+     * property for the source object and the target property for the target
+     * object to start tracking changes, notifies all registered
+     * {@code BindingListeners} that the binding has become bound, and
+     * fires a property change notification to indicate a change to the
+     * {@code "bound"} property.
+     *
+     * @throws UnsupportedOperationException if the {@code Binding} is managed
+     * @throws IllegalStateException if the {@code Binding} is already bound
+     * @see #isBound()
+     * @see #isManaged()
+     * @see #unbind
+     */
+    public final void bind() {
+        throwIfManaged();
+        bindUnmanaged();
+    }
+
+    /**
+     * A protected version of {@link #bind} that allows managed
+     * subclasses to bind without throwing an exception
+     * for being managed.
+     *
+     * @throws IllegalStateException if the {@code Binding} is bound
+     * @see #isManaged()
+     * @see #isBound()
+     */
+    protected final void bindUnmanaged() {
+        throwIfBound();
+
+        bindImpl();
+
+        psl = new PSL();
+        sourceProperty.addPropertyStateListener(sourceObject, psl);
+        targetProperty.addPropertyStateListener(targetObject, psl);
+
+        isBound = true;
+
+        if (listeners != null) {
+            for (BindingListener listener : listeners) {
+                listener.bindingBecameBound(this);
+            }
+        }
+        
+        firePropertyChange("bound", false, true);
+    }
+
+    /**
+     * Called by {@link #bind} to allow subclasses to initiate binding.
+     * Subclasses typically need not install {@code PropertyStateListeners}
+     * on the source property and target property as they will be notified
+     * by calls to {@link #sourceChangedImpl} and {@link #targetChangedImpl}
+     * when the source and target properties change respectively.
+     *
+     * @see #unbindImpl
+     */
+    protected abstract void bindImpl();
+
+    /**
+     * Unbinds this binding. Removes the {@code PropertyStateListeners}
+     * added by {@code bind}, calls {@link #unbindImpl} to allow subclasses
+     * to uninitiate binding, notifies all registered {@code BindingListeners}
+     * that the binding has become unbound, and fires a property change
+     * notification to indicate a change to the {@code "bound"} property.
+     *
+     * @throws UnsupportedOperationException if the {@code Binding} is managed
+     * @throws IllegalStateException if the {@code Binding} is not bound
+     * @see #isBound()
+     * @see #isManaged()
+     * @see #bind
+     */
+    public final void unbind() {
+        throwIfManaged();
+        unbindUnmanaged();
+    }
+
+    /**
+     * A protected version of {@link #unbind} that allows managed
+     * subclasses to unbind without throwing an exception
+     * for being managed.
+     *
+     * @throws IllegalStateException if the {@code Binding} is not bound
+     * @see #isManaged()
+     * @see #isBound()
+     */
+    protected final void unbindUnmanaged() {
+        throwIfUnbound();
+
+        sourceProperty.removePropertyStateListener(sourceObject, psl);
+        targetProperty.removePropertyStateListener(targetObject, psl);
+        psl = null;
+
+        unbindImpl();
+
+        isBound = false;
+
+        if (listeners != null) {
+            for (BindingListener listener : listeners) {
+                listener.bindingBecameUnbound(this);
+            }
+        }
+        
+        firePropertyChange("bound", true, false);
+    }
+
+    /**
+     * Called by {@link #unbind} to allow subclasses to uninitiate binding.
+     *
+     * @see #bindImpl
+     */
+    protected abstract void unbindImpl();
+
+    /**
+     * Returns whether or not this {@code Binding} is bound.
+     * <p>
+     * {@code Binding} fires a property change notification with
+     * property name {@code "bound"} when the value of
+     * this property changes.
+     *
+     * @return whether or not the {@code Binding} is bound
+     * @see #bind
+     * @see #unbind
+     */
+    public final boolean isBound() {
+        return isBound;
+    }
+
+    /**
+     * Sets whether or not this {@code Binding} is managed. Some
+     * {@code Bindings} are managed, often by another {@code Binding}.
+     * A managed {@code Binding} does not allow certain methods to be called by
+     * the user. These methods are identified in their documentation.
+     * Subclasses should call {@code setManaged(true)} to make themselves managed.
+     * {@code Binding} provides protected versions of the managed methods, with the
+     * suffix {@code "Unmanaged"}, for subclasses to use internally without
+     * checking whether or not they are managed.
+     */
+    protected final void setManaged(boolean isManaged) {
+        this.isManaged = isManaged;
+    }
+
+    /**
+     * Returns whether or not this {@code Binding} is managed. Some
+     * {@code Bindings} are managed, often by another {@code Binding}.
+     * A managed {@code Binding} does not allow certain methods to be called by
+     * the user. These methods are identified in their documentation.
+     * Subclasses should call {@code setManaged(true)} to make themselves managed.
+     * {@code Binding} provides protected versions of the managed methods, with the
+     * suffix {@code "Unmanaged"}, for subclasses to use internally without
+     * checking whether or not they are managed.
+     *
+     * @return whether or not the {@code Binding} is managed
+     * @see #setManaged
+     */
+    public final boolean isManaged() {
+        return isManaged;
+    }
+
+    /**
+     * Notifies all registered {@code BindingListeners} of a successful
+     * sync ({@code refresh} or {@code save}), by calling {@code synced}
+     * on each one.
+     */
+    protected final void notifySynced() {
+        if (listeners == null) {
+            return;
+        }
+
+        for (BindingListener listener : listeners) {
+            listener.synced(this);
+        }
+    }
+
+    /**
+     * Notifies all registered {@code BindingListeners} of a failure to
+     * sync ({@code refresh} or {@code save}), by calling
+     * {@code syncFailed} on each one.
+     *
+     * @param failure the reason that the sync failed
+     */
+    protected final void notifySyncFailed(SyncFailure failure) {
+        if (listeners == null) {
+            return;
+        }
+
+        for (BindingListener listener : listeners) {
+            listener.syncFailed(this, failure);
+        }
+    }
+
+    private final SyncFailure notifyAndReturn(SyncFailure failure) {
+        if (failure == null) {
+            notifySynced();
+        } else {
+            notifySyncFailed(failure);
+        }
+
+        return failure;
+    }
+
+    /**
+     * The same as {@link #refresh} with the additional
+     * behavior of notifying all registered {@code BindingListeners}
+     * with {@code synced} if {@code refresh} returns {@code null}
+     * or {@code syncFailed} if {@code refresh} returns a
+     * {@code SyncFailure}.
+     *
+     * @return the return value from the call to {@code refresh}
+     * @throws UnsupportedOperationException if the {@code Binding} is managed
+     * @throws RuntimeException as specified by {@link #refresh}
+     * @throws ClassCastException as specified by {@link #refresh}
+     * @see #isManaged()
+     */
+    public final SyncFailure refreshAndNotify() {
+        return notifyAndReturn(refresh());
+    }
+
+    /**
+     * A protected version of {@link #refreshAndNotify} that allows managed
+     * subclasses to refresh and notify without throwing an exception
+     * for being managed.
+     *
+     * @return the return value from the call to {@code refresh}
+     * @throws RuntimeException as specified by {@link #refresh}
+     * @throws ClassCastException as specified by {@link #refresh}
+     * @see #isManaged()
+     */
+    protected final SyncFailure refreshAndNotifyUnmanaged() {
+        return notifyAndReturn(refreshUnmanaged());
+    }
+    
+    /**
+     * The same as {@link #save} with the additional
+     * behavior of notifying all registered {@code BindingListeners}
+     * with {@code synced} if {@code save} returns {@code null}
+     * or {@code syncFailed} if {@code save} returns a
+     * {@code SyncFailure}.
+     *
+     * @return the return value from the call to {@code save}
+     * @throws UnsupportedOperationException if the {@code Binding} is managed
+     * @throws ClassCastException as specified by {@link #refresh}
+     * @see #isManaged()
+     */
+    public final SyncFailure saveAndNotify() {
+        return notifyAndReturn(save());
+    }
+
+    /**
+     * A protected version of {@link #saveAndNotify} that allows managed
+     * subclasses to save and notify without throwing an exception
+     * for being managed.
+     *
+     * @return the return value from the call to {@code save}
+     * @throws ClassCastException as specified by {@link #save}
+     * @see #isManaged()
+     */
+    protected final SyncFailure saveAndNotifyUnmanaged() {
+        return notifyAndReturn(saveUnmanaged());
+    }
+
+    /**
+     * Fetches the value of the source property for the source object and sets
+     * it as the value of the target property for the target object.
+     * First calls {@link #getSourceValueForTarget}. If the return value
+     * from that method represents a failure, this method returns the failure.
+     * Otherwise, it calls {@code setValue} on the target property for the
+     * target object with the value obtained from the source.
+     *
+     * @return the reason for failure if the binding could not be refreshed,
+     *         or {@code null} for success
+     * @throws UnsupportedOperationException if the {@code Binding} is managed
+     * @throws RuntimeException if thrown by {@link #getSourceValueForTarget}
+     * @throws ClassCastException if thrown by {@link #getSourceValueForTarget}
+     * @see #isManaged()
+     * @see #save
+     */
+    public final SyncFailure refresh() {
+        throwIfManaged();
+        return refreshUnmanaged();
+    }
+
+    /**
+     * A protected version of {@link #refresh} that allows managed
+     * subclasses to refresh without throwing an exception
+     * for being managed.
+     *
+     * @return the reason for failure if the binding could not be refreshed,
+     *         or {@code null} for success
+     * @throws RuntimeException if thrown by {@link #getSourceValueForTarget}
+     * @throws ClassCastException if thrown by {@link #getSourceValueForTarget}
+     * @see #isManaged()
+     */
+    protected final SyncFailure refreshUnmanaged() {
+        ValueResult<TV> vr = getSourceValueForTarget();
+        if (vr.failed()) {
+            return vr.getFailure();
+        }
+
+        try {
+            ignoreChange = true;
+            targetProperty.setValue(targetObject, vr.getValue());
+        } finally {
+            ignoreChange = false;
+        }
+
+        return null;
+    }
+
+    /**
+     * Fetches the value of the target property for the target object and sets
+     * it as the value of the source property for the source object.
+     * First calls {@link #getTargetValueForSource}. If the return value
+     * from that method represents a failure, this method returns the failure.
+     * Otherwise, it calls {@code setValue} on the source property for the
+     * source object with the value obtained from the target.
+     *
+     * @return the reason for failure if the binding could not be saved,
+     *         or {@code null} for success
+     * @throws UnsupportedOperationException if the {@code Binding} is managed
+     * @throws ClassCastException if thrown by {@link #getTargetValueForSource}
+     * @see #isManaged()
+     * @see #refresh
+     */
+    public final SyncFailure save() {
+        throwIfManaged();
+        return saveUnmanaged();
+    }
+
+    /**
+     * A protected version of {@link #save} that allows managed
+     * subclasses to save without throwing an exception
+     * for being managed.
+     *
+     * @return the reason for failure if the binding could not be saved,
+     *         or {@code null} for success
+     * @throws ClassCastException if thrown by {@link #getTargetValueForSource}
+     * @see #isManaged()
+     */
+    protected final SyncFailure saveUnmanaged() {
+        ValueResult<SV> vr = getTargetValueForSource();
+        if (vr.failed()) {
+            return vr.getFailure();
+        }
+
+        try {
+            ignoreChange = true;
+            sourceProperty.setValue(sourceObject, vr.getValue());
+        } finally {
+            ignoreChange = false;
+        }
+
+        return null;
+    }
+
+    private final Class<?> noPrimitiveType(Class<?> klass) {
+        if (!klass.isPrimitive()) {
+            return klass;
+        }
+
+        if (klass == Byte.TYPE) {
+            return Byte.class;
+        } else if (klass == Short.TYPE) {
+            return Short.class;
+        } else if (klass == Integer.TYPE) {
+            return Integer.class;
+        } else if (klass == Long.TYPE) {
+            return Long.class;
+        } else if (klass == Boolean.TYPE) {
+            return Boolean.class;
+        } else if (klass == Character.TYPE) {
+            return Character.class;
+        } else if (klass == Float.TYPE) {
+            return Float.class;
+        } else if (klass == Double.TYPE) {
+            return Double.class;
+        }
+
+        throw new AssertionError();
+    }
+
+    private final TV convertForward(SV value) {
+        if (converter == null) {
+            Class<?> targetType = noPrimitiveType(targetProperty.getWriteType(targetObject));
+            return (TV)targetType.cast(Converter.defaultConvert(value, targetType));
+        }
+
+        return converter.convertForward(value);
+    }
+
+    private final SV convertReverse(TV value) {
+        if (converter == null) {
+            Class<?> sourceType = noPrimitiveType(sourceProperty.getWriteType(sourceObject));
+            return (SV)sourceType.cast(Converter.defaultConvert(value, sourceType));
+        }
+
+        return converter.convertReverse(value);
+    }
+
+    /**
+     * Throws an UnsupportedOperationException if the {@code Binding} is managed.
+     * Useful for calling at the beginning of method implementations that
+     * shouldn't be called on managed {@code Bindings}
+     *
+     * @throws UnsupportedOperationException if the {@code Binding} is managed
+     * @see #isManaged()
+     */
+    protected final void throwIfManaged() {
+        if (isManaged()) {
+            throw new UnsupportedOperationException("Can not call this method on a managed binding");
+        }
+    }
+    
+    /**
+     * Throws an IllegalStateException if the {@code Binding} is bound.
+     * Useful for calling at the beginning of method implementations that
+     * shouldn't be called when the {@code Binding} is bound.
+     *
+     * @throws IllegalStateException if the {@code Binding} is bound.
+     */
+    protected final void throwIfBound() {
+        if (isBound()) {
+            throw new IllegalStateException("Can not call this method on a bound binding");
+        }
+    }
+
+    /**
+     * Throws an IllegalStateException if the {@code Binding} is unbound.
+     * Useful for calling at the beginning of method implementations that should
+     * only be called when the {@code Binding} is bound.
+     *
+     * @throws IllegalStateException if the {@code Binding} is unbound.
+     */
+    protected final void throwIfUnbound() {
+        if (!isBound()) {
+            throw new IllegalStateException("Can not call this method on an unbound binding");
+        }
+    }
+
+    /**
+     * Returns a string representation of the {@code Binding}. This
+     * method is intended to be used for debugging purposes only, and
+     * the content and format of the returned string may vary between
+     * implementations. The returned string may be empty but may not
+     * be {@code null}.
+     *
+     * @return a string representation of this {@code Binding}
+     */
+    public String toString() {
+        return getClass().getName() + " [" + paramString() + "]";
+    }
+
+    /**
+     * Returns a string representing the internal state of the {@code Binding}.
+     * This method is intended to be used for debugging purposes only,
+     * and the content and format of the returned string may vary between
+     * implementations. The returned string may be empty but may not
+     * be {@code null}.
+     *
+     * @return a string representing the state of the {@code Binding}.
+     */
+    protected String paramString() {
+        return "name=" + getName() +
+               ", sourceObject=" + sourceObject +
+               ", sourceProperty=" + sourceProperty +
+               ", targetObject=" + targetObject +
+               ", targetProperty=" + targetProperty +
+               ", validator=" + validator +
+               ", converter=" + converter +
+               ", sourceNullValue=" + sourceNullValue +
+               ", targetNullValue=" + targetNullValue +
+               ", sourceUnreadableValueSet=" + sourceUnreadableValueSet +
+               ", sourceUnreadableValue=" + sourceUnreadableValue +
+               ", bound=" + isBound;
+    }
+    
+    private void sourceChanged(PropertyStateEvent pse) {
+        if (listeners != null) {
+            for (BindingListener listener : listeners) {
+                listener.sourceChanged(this, pse);
+            }
+        }
+
+        sourceChangedImpl(pse);
+    }
+
+    /**
+     * Called to indicate that the source property has fired a
+     * {@code PropertyStateEvent} to indicate that its state has changed for
+     * the source object. Called after the {@code Binding} has notified
+     * any property change listeners and {@code BindingListeners} that
+     * the source value has been edited (only if the {@code PropertyStateEvent}
+     * represents a value change). This method is useful for subclasses
+     * to detect source changes and perform syncing as appropriate.
+     */
+    protected void sourceChangedImpl(PropertyStateEvent pse) {
+    }
+
+    private void targetChanged(PropertyStateEvent pse) {
+        if (listeners != null) {
+            for (BindingListener listener : listeners) {
+                listener.targetChanged(this, pse);
+            }
+        }
+
+        targetChangedImpl(pse);
+    }
+
+    /**
+     * Called to indicate that the target property has fired a
+     * {@code PropertyStateEvent} to indicate that its state has changed for
+     * the target object. Called after the {@code Binding} has notified
+     * any property change listeners and {@code BindingListeners} that
+     * the target value has been edited (only if the {@code PropertyStateEvent}
+     * represents a value change). This method is useful for subclasses
+     * to detect target changes and perform syncing as appropriate.
+     */
+    protected void targetChangedImpl(PropertyStateEvent pse) {
+    }
+
+    /**
+     * Adds a {@code PropertyChangeListener} to be notified when any property of
+     * this {@code Binding} changes. Does nothing if the listener is
+     * {@code null}. If a listener is added more than once, notifications are
+     * sent to that listener once for every time that it has been added.
+     * The ordering of listener notification is unspecified.
+     * <p>
+     * {@code Binding} fires property change notification for the following
+     * properties:
+     * <p>
+     * <ul>
+     *    <li>{@code sourceProperty}
+     *    <li>{@code targetProperty}
+     *    <li>{@code sourceObject}
+     *    <li>{@code targetObject}
+     *    <li>{@code validator}
+     *    <li>{@code converter}
+     *    <li>{@code sourceNullValue}
+     *    <li>{@code targetNullValue}
+     *    <li>{@code sourceUnreadableValueSet}
+     *    <li>{@code sourceUnreadableValue}
+     *    <li>{@code bound}
+     * </ul>
+     * <p>
+     * For other types of {@code Binding} notifications register a
+     * {@code BindingListener}.
+     *
+     * @param listener the listener to add
+     * @see #addBindingListener
+     */
+    public final void addPropertyChangeListener(PropertyChangeListener listener) {
+        if (changeSupport == null) {
+            changeSupport = new PropertyChangeSupport(this);
+        }
+
+        changeSupport.addPropertyChangeListener(listener);
+    }
+
+    /**
+     * Adds a {@code PropertyChangeListener} to be notified when the property identified
+     * by the {@code propertyName} argument changes on this {@code Binding}.
+     * Does nothing if the property name or listener is {@code null}.
+     * If a listener is added more than once, notifications are
+     * sent to that listener once for every time that it has been added.
+     * The ordering of listener notification is unspecified.
+     * <p>
+     * {@code Binding} fires property change notification for the following
+     * properties:
+     * <p>
+     * <ul>
+     *    <li>{@code sourceProperty}
+     *    <li>{@code targetProperty}
+     *    <li>{@code sourceObject}
+     *    <li>{@code targetObject}
+     *    <li>{@code validator}
+     *    <li>{@code converter}
+     *    <li>{@code sourceNullValue}
+     *    <li>{@code targetNullValue}
+     *    <li>{@code sourceUnreadableValueSet}
+     *    <li>{@code sourceUnreadableValue}
+     *    <li>{@code bound}
+     * </ul>
+     * <p>
+     * For other types of {@code Binding} notifications register a
+     * {@code BindingListener}.
+     *
+     * @param propertyName the name of the property to listen for changes on
+     * @param listener the listener to add
+     */
+    public final void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
+        if (changeSupport == null) {
+            changeSupport = new PropertyChangeSupport(this);
+        }
+
+        changeSupport.addPropertyChangeListener(propertyName, listener);
+    }
+
+    /**
+     * Removes a {@code PropertyChangeListener} from the {@code Binding}. Does
+     * nothing if the listener is {@code null} or is not one of those registered.
+     * If the listener being removed was registered more than once, only one
+     * occurrence of the listener is removed from the list of listeners.
+     * The ordering of listener notification is unspecified.
+     *
+     * @param listener the listener to remove
+     * @see #addPropertyChangeListener
+     */
+    public final void removePropertyChangeListener(PropertyChangeListener listener) {
+        if (changeSupport == null) {
+            return;
+        }
+
+        changeSupport.removePropertyChangeListener(listener);
+    }
+
+    /**
+     * Removes a {@code PropertyChangeListener} from the {@code Binding} for the given
+     * property name. Does nothing if the property name or listener is
+     * {@code null} or the listener is not one of those registered.
+     * If the listener being removed was registered more than once, only one
+     * occurrence of the listener is removed from the list of listeners.
+     * The ordering of listener notification is unspecified.
+     * 
+     * @param propertyName the name of the property to remove the listener for
+     * @param listener the listener to remove
+     * @see #addPropertyChangeListener(String, PropertyChangeListener)
+     */
+    public final void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
+        if (changeSupport == null) {
+            return;
+        }
+
+        changeSupport.removePropertyChangeListener(propertyName, listener);
+    }
+
+    /**
+     * Returns the list of {@code PropertyChangeListeners} registered on this
+     * {@code Binding}. Order is undefined. Returns an empty array if there are
+     * no listeners.
+     *
+     * @return the list of {@code PropertyChangeListeners} registered on this {@code Binding}
+     * @see #addPropertyChangeListener
+     */
+    public final PropertyChangeListener[] getPropertyChangeListeners() {
+        if (changeSupport == null) {
+            return new PropertyChangeListener[0];
+        }
+        
+        return changeSupport.getPropertyChangeListeners();
+    }
+
+    /**
+     * Returns the list of {@code PropertyChangeListeners} registered on this
+     * {@code Binding} for the given property name. Order is undefined. Returns an empty array
+     * if there are no listeners registered for the property name.
+     *
+     * @param propertyName the property name to retrieve the listeners for
+     * @return the list of {@code PropertyChangeListeners} registered on this {@code Binding}
+     *         for the given property name
+     * @see #addPropertyChangeListener(String, PropertyChangeListener)
+     */
+    public final PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {
+        if (changeSupport == null) {
+            return new PropertyChangeListener[0];
+        }
+        
+        return changeSupport.getPropertyChangeListeners(propertyName);
+    }
+
+    /**
+     * Sends a {@code PropertyChangeEvent} to the {@code PropertyChangeListeners}
+     * registered on the {@code Binding}.
+     *
+     * @param propertyName the name of the property that's changed
+     * @param oldValue the old value of the property
+     * @param newValue the new value of the property
+     */
+    protected final void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
+        if (changeSupport != null) {
+            changeSupport.firePropertyChange(propertyName, oldValue, newValue);
+        }
+    }
+
+    private class PSL implements PropertyStateListener {
+        public void propertyStateChanged(PropertyStateEvent pse) {
+            if (ignoreChange) {
+                return;
+            }
+
+            if (pse.getSourceProperty() == sourceProperty && pse.getSourceObject() == sourceObject) {
+                sourceChanged(pse);
+            } else {
+                targetChanged(pse);
+            }
+        }
+    }
+
+}
diff --git a/src/org/jdesktop/beansbinding/BindingGroup.java b/src/org/jdesktop/beansbinding/BindingGroup.java
new file mode 100644
index 0000000..4a0de65
--- /dev/null
+++ b/src/org/jdesktop/beansbinding/BindingGroup.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.beansbinding;
+
+import java.util.*;
+
+/**
+ * {@code BindingGroup} allows you to create a group of {@code Bindings}
+ * and operate on and/or track state changes to the {@code Bindings} as
+ * a group.
+ *
+ * @author Shannon Hickey
+ */
+public class BindingGroup {
+    private final List<Binding> unbound = new ArrayList<Binding>();
+    private final List<Binding> bound = new ArrayList<Binding>();
+    private List<BindingListener> listeners;
+    private Handler handler;
+    private Map<String, Binding> namedBindings;
+
+    /**
+     * Creates an empty {@code BindingGroup}.
+     */
+    public BindingGroup() {}
+
+    /**
+     * Adds a {@code Binding} to this group.
+     *
+     * @param binding the {@code Binding} to add
+     * @throws IllegalArgumentException if the binding is null, is a managed binding,
+     *         if the group already contains this binding, or if the group already
+     *         contains a binding with the same ({@code non-null}) name
+     */
+    public final void addBinding(Binding binding) {
+        if (binding == null) {
+            throw new IllegalArgumentException("Binding must be non-null");
+        }
+
+        if (binding.isManaged()) {
+            throw new IllegalArgumentException("Managed bindings can't be in a group");
+        }
+
+        if (bound.contains(binding) || unbound.contains(binding)) {
+            throw new IllegalArgumentException("Group already contains this binding");
+        }
+
+        String name = binding.getName();
+        if (name != null) {
+            if (getBinding(name) != null) {
+                throw new IllegalArgumentException("Context already contains a binding with name \"" + name + "\"");
+            } else {
+                putNamed(name, binding);
+            }
+        }
+
+        binding.addBindingListener(getHandler());
+
+        if (binding.isBound()) {
+            bound.add(binding);
+        } else {
+            unbound.add(binding);
+        }
+    }
+
+    /**
+     * Removes a {@code Binding} from this group.
+     *
+     * @param binding the {@code Binding} to remove
+     * @throws IllegalArgumentException if the binding is null or
+     *         if the group doesn't contain this binding
+     */
+    public final void removeBinding(Binding binding) {
+        if (binding == null) {
+            throw new IllegalArgumentException("Binding must be non-null");
+        }
+
+        if (binding.isBound()) {
+            if (!bound.remove(binding)) {
+                throw new IllegalArgumentException("Unknown Binding");
+            }
+        } else {
+            if (!unbound.remove(binding)) {
+                throw new IllegalArgumentException("Unknown Binding");
+            }
+        }
+
+        String name = binding.getName();
+        if (name != null) {
+            assert namedBindings != null;
+            namedBindings.remove(name);
+        }
+
+        binding.removeBindingListener(getHandler());
+    }
+
+    private void putNamed(String name, Binding binding) {
+        if (namedBindings == null) {
+            namedBindings = new HashMap<String, Binding>();
+        }
+
+        namedBindings.put(name, binding);
+    }
+
+    /**
+     * Returns the {@code Binding} in this group with the given name,
+     * or {@code null} if this group doesn't contain a {@code Binding}
+     * with the given name.
+     *
+     * @param name the name of the {@code Binding} to fetch
+     * @return the {@code Binding} in this group with the given name,
+     *         or {@code null}
+     * @throws IllegalArgumentException if {@code name} is {@code null}
+     */
+    public final Binding getBinding(String name) {
+        if (name == null) {
+            throw new IllegalArgumentException("cannot fetch unnamed bindings");
+        }
+
+        return namedBindings == null ? null : namedBindings.get(name);
+    }
+
+    /**
+     * Returns a list of all {@code Bindings} in this group. Order is undefined.
+     * Returns an empty list if the group contains no {@code Bindings}.
+     *
+     * @return a list of all {@code Bindings} in this group.
+     */
+    public final List<Binding> getBindings() {
+        ArrayList list = new ArrayList(bound);
+        list.addAll(unbound);
+        return Collections.unmodifiableList(list);
+    }
+
+    /**
+     * Calls {@code bind} on all unbound bindings in the group.
+     */
+    public void bind() {
+        List<Binding> toBind = new ArrayList<Binding>(unbound);
+        for (Binding binding : toBind) {
+            binding.bind();
+        }
+    }
+
+    /**
+     * Calls {@code unbind} on all bound bindings in the group.
+     */
+    public void unbind() {
+        List<Binding> toUnbind = new ArrayList<Binding>(bound);
+        for (Binding binding : toUnbind) {
+            binding.unbind();
+        }
+    }
+
+    /**
+     * Adds a {@code BindingListener} to be notified of all {@code BindingListener}
+     * notifications fired by any {@code Binding} in the group. Does nothing if
+     * the listener is {@code null}. If a listener is added more than once,
+     * notifications are sent to that listener once for every time that it has
+     * been added. The ordering of listener notification is unspecified.
+     *
+     * @param listener the listener to add
+     */
+    public final void addBindingListener(BindingListener listener) {
+        if (listener == null) {
+            return;
+        }
+
+        if (listeners == null) {
+            listeners = new ArrayList<BindingListener>();
+        }
+
+        listeners.add(listener);
+    }
+
+    /**
+     * Removes a {@code BindingListener} from the group. Does
+     * nothing if the listener is {@code null} or is not one of those registered.
+     * If the listener being removed was registered more than once, only one
+     * occurrence of the listener is removed from the list of listeners.
+     * The ordering of listener notification is unspecified.
+     *
+     * @param listener the listener to remove
+     * @see #addBindingListener
+     */
+    public final void removeBindingListener(BindingListener listener) {
+        if (listener == null) {
+            return;
+        }
+
+        if (listeners != null) {
+            listeners.remove(listener);
+        }
+    }
+
+    /**
+     * Returns the list of {@code BindingListeners} registered on this
+     * group. Order is undefined. Returns an empty array if there are
+     * no listeners.
+     *
+     * @return the list of {@code BindingListeners} registered on this group
+     * @see #addBindingListener
+     */
+    public final BindingListener[] getBindingListeners() {
+        if (listeners == null) {
+            return new BindingListener[0];
+        }
+
+        BindingListener[] ret = new BindingListener[listeners.size()];
+        ret = listeners.toArray(ret);
+        return ret;
+    }
+
+    private final Handler getHandler() {
+        if (handler == null) {
+            handler = new Handler();
+        }
+
+        return handler;
+    }
+
+    private class Handler implements BindingListener {
+        public void syncFailed(Binding binding, Binding.SyncFailure failure) {
+            if (listeners == null) {
+                return;
+            }
+
+            for (BindingListener listener : listeners) {
+                listener.syncFailed(binding, failure);
+            }
+        }
+
+        public void synced(Binding binding) {
+            if (listeners == null) {
+                return;
+            }
+
+            for (BindingListener listener : listeners) {
+                listener.synced(binding);
+            }
+        }
+
+        public void sourceChanged(Binding binding, PropertyStateEvent event) {
+            if (listeners == null) {
+                return;
+            }
+            
+            for (BindingListener listener : listeners) {
+                listener.sourceChanged(binding, event);
+            }
+        }
+
+        public void targetChanged(Binding binding, PropertyStateEvent event) {
+            if (listeners == null) {
+                return;
+            }
+
+            for (BindingListener listener : listeners) {
+                listener.targetChanged(binding, event);
+            }
+        }
+
+        public void bindingBecameBound(Binding binding) {
+            unbound.remove(binding);
+            bound.add(binding);
+
+            if (listeners == null) {
+                return;
+            }
+            
+            for (BindingListener listener : listeners) {
+                listener.bindingBecameBound(binding);
+            }
+        }
+
+        public void bindingBecameUnbound(Binding binding) {
+            bound.remove(binding);
+            unbound.add(binding);
+
+            if (listeners == null) {
+                return;
+            }
+            
+            for (BindingListener listener : listeners) {
+                listener.bindingBecameUnbound(binding);
+            }
+        }
+    }
+
+}
diff --git a/src/org/jdesktop/beansbinding/BindingListener.java b/src/org/jdesktop/beansbinding/BindingListener.java
new file mode 100644
index 0000000..a007e0f
--- /dev/null
+++ b/src/org/jdesktop/beansbinding/BindingListener.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.beansbinding;
+
+import java.util.EventListener;
+
+/**
+ * {@code BindingListeners} are registered on {@code Bindings} or {@code BindingGroups}
+ * to listen for changes to the state of {@code Bindings}
+ *
+ * @see Binding
+ * @see BindingGroup
+ *
+ * @author Shannon Hickey
+ */
+public interface BindingListener extends EventListener {
+    
+    /**
+     * Notification that a {@code Binding} has been bound.
+     *
+     * @param binding the {@code Binding}
+     */
+    public void bindingBecameBound(Binding binding);
+
+    /**
+     * Notification that a {@code Binding} has been unbound.
+     *
+     * @param binding the {@code Binding}
+     */
+    public void bindingBecameUnbound(Binding binding);
+
+    /**
+     * Notification that the {@code Binding} attempted to sync the source and
+     * target, but the sync failed.
+     *
+     * @param binding the {@code Binding}
+     * @param failure the reason the sync failed
+     */
+    public void syncFailed(Binding binding, Binding.SyncFailure failure);
+
+    /**
+     * Notification that the source and target of a {@code Binding} have
+     * been made in sync.
+     *
+     * @param binding the {@code Binding}
+     */
+    public void synced(Binding binding);
+
+    /**
+     * Notification that the source property of a {@code Binding} has fired
+     * a {@code PropertyStateEvent} for the {@code Binding's} source object.
+     *
+     * @param binding the {@code Binding}
+     */
+    public void sourceChanged(Binding binding, PropertyStateEvent event);
+
+    /**
+     * Notification that the target property of a {@code Binding} has fired
+     * a {@code PropertyStateEvent} for the {@code Binding's} target object.
+     *
+     * @param binding the {@code Binding}
+     */
+    public void targetChanged(Binding binding, PropertyStateEvent event);
+}
diff --git a/src/org/jdesktop/beansbinding/Bindings.java b/src/org/jdesktop/beansbinding/Bindings.java
new file mode 100644
index 0000000..a6178fb
--- /dev/null
+++ b/src/org/jdesktop/beansbinding/Bindings.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.beansbinding;
+
+/**
+ * A factory class for creating instances of the concrete {@code Binding}
+ * implementations provided by this package.
+ *
+ * @author Shannon Hickey
+ */
+public class Bindings {
+
+    private Bindings() {}
+
+    /**
+     * Creates an instance of {@code AutoBinding} that binds a source object to a property of a target object.
+     * The {@code AutoBinding's} source property is set to an instance of {@code ObjectProperty} so that the
+     * source object is used directly, rather than some property of the source object.
+     * 
+     * @param strategy the update strategy for the binding
+     * @param sourceObject the source object
+     * @param targetObject the target object
+     * @param targetProperty the target property
+     * @return an {@code AutoBinding} that binds the source object to the target property of the target object
+     * @throws IllegalArgumentException if the update strategy or target property is {@code null}
+     */
+    public static <SS, TS, TV> AutoBinding<SS, SS, TS, TV> createAutoBinding(AutoBinding.UpdateStrategy strategy, SS sourceObject, TS targetObject, Property<TS, TV> targetProperty) {
+        return new AutoBinding<SS, SS, TS, TV>(strategy, sourceObject, ObjectProperty.<SS>create(), targetObject, targetProperty, null);
+    }
+
+    /**
+     * Creates a named instance of {@code AutoBinding} that binds a source object to a property of a target object.
+     * The {@code AutoBinding's} source property is set to an instance of {@code ObjectProperty} so that the
+     * source object is used directly, rather than some property of the source object.
+     * 
+     * @param strategy the update strategy for the binding
+     * @param sourceObject the source object
+     * @param targetObject the target object
+     * @param targetProperty the target property
+     * @param name a name for the binding
+     * @return an {@code AutoBinding} that binds the source object to the target property of the target object
+     * @throws IllegalArgumentException if the update strategy or target property is {@code null}
+     */
+    public static <SS, TS, TV> AutoBinding<SS, SS, TS, TV> createAutoBinding(AutoBinding.UpdateStrategy strategy, SS sourceObject, TS targetObject, Property<TS, TV> targetProperty, String name) {
+        return new AutoBinding<SS, SS, TS, TV>(strategy, sourceObject, ObjectProperty.<SS>create(), targetObject, targetProperty, name);
+    }
+
+    /**
+     * Creates an instance of {@code AutoBinding} that binds a property of a source object to a property of a target object.
+     * 
+     * @param strategy the update strategy for the binding
+     * @param sourceObject the source object
+     * @param sourceProperty the source property
+     * @param targetObject the target object
+     * @param targetProperty the target property
+     * @return an {@code AutoBinding} that binds the source object to the target property of the target object
+     * @throws IllegalArgumentException if the update strategy, source property or target property is {@code null}
+     */
+    public static <SS, SV, TS, TV> AutoBinding<SS, SV, TS, TV> createAutoBinding(AutoBinding.UpdateStrategy strategy, SS sourceObject, Property<SS, SV> sourceProperty, TS targetObject, Property<TS, TV> targetProperty) {
+        return new AutoBinding<SS, SV, TS, TV>(strategy, sourceObject, sourceProperty, targetObject, targetProperty, null);
+    }
+
+    /**
+     * Creates a named instance of {@code AutoBinding} that binds a property of a source object to a property of a target object.
+     * 
+     * @param strategy the update strategy for the binding
+     * @param sourceObject the source object
+     * @param sourceProperty the source property
+     * @param targetObject the target object
+     * @param targetProperty the target property
+     * @param name a name for the binding
+     * @return an {@code AutoBinding} that binds the source object to the target property of the target object
+     * @throws IllegalArgumentException if the update strategy, source property or target property is {@code null}
+     */
+    public static <SS, SV, TS, TV> AutoBinding<SS, SV, TS, TV> createAutoBinding(AutoBinding.UpdateStrategy strategy, SS sourceObject, Property<SS, SV> sourceProperty, TS targetObject, Property<TS, TV> targetProperty, String name) {
+        return new AutoBinding<SS, SV, TS, TV>(strategy, sourceObject, sourceProperty, targetObject, targetProperty, name);
+    }
+
+}
diff --git a/src/org/jdesktop/beansbinding/Converter.java b/src/org/jdesktop/beansbinding/Converter.java
new file mode 100644
index 0000000..2d58629
--- /dev/null
+++ b/src/org/jdesktop/beansbinding/Converter.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.beansbinding;
+
+import java.math.BigInteger;
+import java.math.BigDecimal;
+
+/**
+ * {@code Converter} is responsible for converting a value from one type
+ * to another.
+ * <p>
+ * The conversion methods can throw {@code RuntimeExceptions} in response
+ * to a problem in conversion. For example, a {@code String} to {@code Integer}
+ * converter might throw a {@code NumberFormatException} if the {@code String}
+ * can't be parsed properly into an {@code Integer}.
+ *
+ * @param <S> the {@code Converter's} source type
+ * @param <T> the {@code Converter's} target type
+ *
+ * @author Shannon Hickey
+ * @author Jan Stola
+ * @author Scott Violet
+ */
+public abstract class Converter<S, T> {
+
+    /**
+     * Converts a value from the source type to the target type.
+     * Can throw a {@code RuntimeException} to indicate a problem
+     * with the conversion.
+     *
+     * @param value the source value to convert
+     * @return the value, converted to the target type
+     */
+    public abstract T convertForward(S value);
+
+    /**
+     * Converts a value from the target type to the source type.
+     * Can throw a {@code RuntimeException} to indicate a problem
+     * with the conversion.
+     *
+     * @param value the target value to convert
+     * @return the value, converted to the source type
+     */
+    public abstract S convertReverse(T value);
+
+    static final Converter BYTE_TO_STRING_CONVERTER = new Converter() {
+        public Object convertForward(Object value) {
+            return Byte.toString((Byte)value);
+        }
+
+        public Object convertReverse(Object value) {
+            return Byte.parseByte((String)value);
+        }
+    };
+
+    static final Converter SHORT_TO_STRING_CONVERTER = new Converter() {
+        public Object convertForward(Object value) {
+            return Short.toString((Short)value);
+        }
+
+        public Object convertReverse(Object value) {
+            return Short.parseShort((String)value);
+        }
+    };
+
+    static final Converter INT_TO_STRING_CONVERTER = new Converter() {
+        public Object convertForward(Object value) {
+            return Integer.toString((Integer)value);
+        }
+
+        public Object convertReverse(Object value) {
+            return Integer.parseInt((String)value);
+        }
+    };
+
+    static final Converter LONG_TO_STRING_CONVERTER = new Converter() {
+        public Object convertForward(Object value) {
+            return Long.toString((Long)value);
+        }
+
+        public Object convertReverse(Object value) {
+            return Long.parseLong((String)value);
+        }
+    };
+
+    static final Converter FLOAT_TO_STRING_CONVERTER = new Converter() {
+        public Object convertForward(Object value) {
+            return Float.toString((Float)value);
+        }
+
+        public Object convertReverse(Object value) {
+            return Float.parseFloat((String)value);
+        }
+    };
+
+    static final Converter DOUBLE_TO_STRING_CONVERTER = new Converter() {
+        public Object convertForward(Object value) {
+            return Double.toString((Double)value);
+        }
+
+        public Object convertReverse(Object value) {
+            return Double.parseDouble((String)value);
+        }
+    };
+
+    static final Converter CHAR_TO_STRING_CONVERTER = new Converter() {
+        public Object convertForward(Object value) {
+            return ((Character)value).toString();
+        }
+
+        public Object convertReverse(Object value) {
+            String strVal = (String)value;
+
+            if (strVal.length() != 1) {
+                throw new IllegalArgumentException("String doesn't represent a char");
+            }
+
+            return strVal.charAt(0);
+        }
+    };
+
+    static final Converter BOOLEAN_TO_STRING_CONVERTER = new Converter() {
+        public Object convertForward(Object value) {
+            return ((Boolean)value).toString();
+        }
+
+        public Object convertReverse(Object value) {
+            return new Boolean((String)value);
+        }
+    };
+
+    static final Converter INT_TO_BOOLEAN_CONVERTER = new Converter() {
+        public Object convertForward(Object value) {
+            if (((Integer)value).intValue() == 0) {
+                return Boolean.FALSE;
+            }
+            return Boolean.TRUE;
+        }
+
+        public Object convertReverse(Object value) {
+            if (((Boolean)value).booleanValue()) {
+                return 1;
+            }
+            return 0;
+        }
+    };
+
+    static final Converter BIGINTEGER_TO_STRING_CONVERTER = new Converter() {
+        public Object convertForward(Object value) {
+            return ((BigInteger)value).toString();
+        }
+
+        public Object convertReverse(Object value) {
+            return new BigInteger((String)value);
+        }
+    };
+
+    static final Converter BIGDECIMAL_TO_STRING_CONVERTER = new Converter() {
+        public Object convertForward(Object value) {
+            return ((BigDecimal)value).toString();
+        }
+
+        public Object convertReverse(Object value) {
+            return new BigDecimal((String)value);
+        }
+    };
+
+    static final Object defaultConvert(Object source, Class<?> targetType) {
+        Class sourceType = source.getClass();
+
+        if (sourceType == targetType) {
+            return source;
+        }
+
+        if (targetType == String.class) {
+            if (sourceType == Byte.class) {
+                return BYTE_TO_STRING_CONVERTER.convertForward(source);
+            } else if (sourceType == Short.class) {
+                return SHORT_TO_STRING_CONVERTER.convertForward(source);
+            } else if (sourceType == Integer.class) {
+                return INT_TO_STRING_CONVERTER.convertForward(source);
+            } else if (sourceType == Long.class) {
+                return LONG_TO_STRING_CONVERTER.convertForward(source);
+            } else if (sourceType == Float.class) {
+                return FLOAT_TO_STRING_CONVERTER.convertForward(source);
+            } else if (sourceType == Double.class) {
+                return DOUBLE_TO_STRING_CONVERTER.convertForward(source);
+            } else if (sourceType == Boolean.class) {
+                return BOOLEAN_TO_STRING_CONVERTER.convertForward(source);
+            } else if (sourceType == Character.class) {
+                return CHAR_TO_STRING_CONVERTER.convertForward(source);
+            } else if (sourceType == BigInteger.class) {
+                return BIGINTEGER_TO_STRING_CONVERTER.convertForward(source);
+            } else if (sourceType == BigDecimal.class) {
+                return BIGDECIMAL_TO_STRING_CONVERTER.convertForward(source);
+            }
+        } else if (sourceType == String.class) {
+            if (targetType == Byte.class) {
+                return BYTE_TO_STRING_CONVERTER.convertReverse(source);
+            } else if (targetType == Short.class) {
+                return SHORT_TO_STRING_CONVERTER.convertReverse(source);
+            } else if (targetType == Integer.class) {
+                return INT_TO_STRING_CONVERTER.convertReverse(source);
+            } else if (targetType == Long.class) {
+                return LONG_TO_STRING_CONVERTER.convertReverse(source);
+            } else if (targetType == Float.class) {
+                return FLOAT_TO_STRING_CONVERTER.convertReverse(source);
+            } else if (targetType == Double.class) {
+                return DOUBLE_TO_STRING_CONVERTER.convertReverse(source);
+            } else if (targetType == Boolean.class) {
+                return BOOLEAN_TO_STRING_CONVERTER.convertReverse(source);
+            } else if (targetType == Character.class) {
+                return CHAR_TO_STRING_CONVERTER.convertReverse(source);
+            } else if (targetType == BigInteger.class) {
+                return BIGINTEGER_TO_STRING_CONVERTER.convertReverse(source);
+            } else if (targetType == BigDecimal.class) {
+                return BIGDECIMAL_TO_STRING_CONVERTER.convertReverse(source);
+            }
+        } else if (sourceType == Integer.class && targetType == Boolean.class) {
+            return INT_TO_BOOLEAN_CONVERTER.convertForward(source);
+        } else if (sourceType == Boolean.class && targetType == Integer.class) {
+            return INT_TO_BOOLEAN_CONVERTER.convertReverse(source);
+        }
+
+        return source;
+    }
+}
diff --git a/src/org/jdesktop/beansbinding/ELProperty.java b/src/org/jdesktop/beansbinding/ELProperty.java
new file mode 100644
index 0000000..bedd025
--- /dev/null
+++ b/src/org/jdesktop/beansbinding/ELProperty.java
@@ -0,0 +1,923 @@
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.beansbinding;
+
+import org.jdesktop.el.impl.ExpressionFactoryImpl;
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.ELException;
+import org.jdesktop.el.Expression;
+import org.jdesktop.el.Expression.ResolvedProperty;
+import org.jdesktop.el.ValueExpression;
+import java.beans.*;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.*;
+import org.jdesktop.observablecollections.ObservableMap;
+import org.jdesktop.observablecollections.ObservableMapListener;
+import static org.jdesktop.beansbinding.PropertyStateEvent.UNREADABLE;
+import org.jdesktop.beansbinding.ext.BeanAdapterFactory;
+
+/**
+ * An implementation of {@code Property} that allows Java Beans properties of
+ * source objects to be addressed using a simple dot-separated path syntax
+ * within an EL expression. For example, to create a simple property representing
+ * a {@code Person} bean's mother's {@code firstName}:
+ * <p>
+ * <pre><code>
+ *    ELProperty.create("${mother.firstName}")
+ * </pre></code>
+ * </p>
+ * Note that {@link org.jdesktop.beansbinding.BeanProperty} is more suitable for
+ * such a simple property.
+ * <p> 
+ * To create a property representing the concatenation of a {@code Person} bean's
+ * {@code firstName} and {@code lastName} properties:
+ * <p>
+ * <pre><code>
+ *    ELProperty.create("${firstName} ${lastName}");
+ *</code></pre>
+ * <p>
+ * To create a property that is {@code true} or {@code false} depending
+ * on whether or not the {@code Person's} mother is older than 65:
+ * <p>
+ * <pre><code>
+ *    BeanProperty.create("${mother.age > 65}");
+ * </code></pre>
+ * <p>
+ * Paths specified in the EL expressions are resolved against the source object
+ * with which the property is being used.
+ * <p>
+ * An instance of {@code ELProperty} is immutable and can be used with
+ * different source objects. When a {@code PropertyStateListener} is added to
+ * an {@code ELProperty} for a given source object, the {@code ELProperty}
+ * starts listening to all objects along the paths in the expression (based on that source object)
+ * for change notification, and reflects any changes by notifying the
+ * listener associated with the property for that source object. So, for example,
+ * if a {@code PropertyStateListener} is added to the property from the second example above
+ * for an object {@code Duke}, the {@code PropertyStateListener} is
+ * notified when either {@code Duke's} first name changes, or his last name changes.
+ * If a listener is added to the property from the third example, the {@code PropertyStateListener}
+ * is notified when either a change in {@code Duke's} mother or {@code Duke's} mother's {@code age}
+ * results in a change to the result of the expression.
+ * <p>
+ * It is very important that any bean properties addressed via a {@code ELProperty}
+ * follow the Java Beans specification, including firing property change notification;
+ * otherwise, {@code ELProperty} cannot respond to change. As some beans outside
+ * of your control may not follow the Java Beans specification, {@code ELProperty}
+ * always checks the {@link org.jdesktop.beansbinding.ext.BeanAdapterFactory} to
+ * see if a delegate provider has been registered to provide a delegate bean to take
+ * the place of an object for a given property. See the
+ * <a href="ext/package-summary.html">ext package level</a> documentation for more
+ * details.
+ * <p>
+ * When there are no {@code PropertyStateListeners} installed on an {@code ELProperty}
+ * for a given source, all {@code Property} methods act by evaluating the full expression,
+ * thereby always providing "live" information.
+ * On the contrary, when there are {@code PropertyStateListeners} installed, the beans
+ * along the paths, and the final value, are cached, and only updated upon
+ * notification of change from a bean. Again, this makes it very important that any
+ * bean property that could change along the path fires property change notification.
+ * <i>Note: The {@code setValue} method is currently excluded from the previous
+ * assertion; with the exception of checking the cache to determine if the property is
+ * writeable, it always evaluates the entire expression. The result of this is that
+ * when working with paths containing beans that don't fire property change notification,
+ * you can end up with all methods (including {@code getValue}) working on cached
+ * information, but {@code setValue} working on the live expression. There are plans
+ * to resolve this inconsistency in a future release.</i>
+ * <p>
+ * <a name="READABILITY"><b>Readability</b></a> of an {@code ELProperty} for a given source is defined as follows:
+ * <i>An {@code ELProperty} is readable for a given source if and only if the
+ * following is true for all paths used in the expression:
+ * a) each bean the path, starting with the source, defines a Java Beans getter
+ * method for the the property to be read on it AND b) each bean in the path,
+ * starting with the source and ending with the bean on which we read the final
+ * property, is {@code non-null}. The final value being {@code null} does not
+ * affect the readability.</i>
+ * <p>
+ * So, in the third example given earlier, the {@code ELProperty} is readable for {@code Duke} when all
+ * of the following are true: {@code Duke} defines a Java Beans getter for
+ * {@code mother}, {@code Duke's mother} defines a Java Beans getter for
+ * {@code age}, {@code Duke} is {@code non-null}, {@code Duke's mother}
+ * is {@code non-null}. The {@code ELProperty} is therefore unreadable when
+ * any of the following is true: {@code Duke} does not define a Java Beans
+ * getter for {@code mother}, {@code Duke's mother} does not define a Java
+ * Beans getter for {@code age}, {@code Duke} is {@code null},
+ * {@code Duke's mother} is {@code null}.
+ * <p>
+ * <a name="WRITEABILITY"><b>Writeability</b></a> of an {@code ELProperty} for a given source is defined as follows:
+ * <i>An {@code ELProperty} is writeable for a given source if and only if
+ * a) the EL expression itself is not read-only
+ * (ie. it is a simple expression involving one path such as "${foo.bar.baz}" AND
+ * b) each bean in the path, starting with the source and ending with the bean on
+ * which we set the final property, defines a Java Beans getter method for the
+ * property to be read on it AND c) the bean on which we set the final property
+ * defines a Java Beans setter for the property to be set on it AND d) each bean
+ * in the path, starting with the source and ending with the bean on which we
+ * set the final property, is {@code non-null}. The final value being {@code null}
+ * does not affect the writeability.</i>
+ * <p>
+ * So in the first example given earlier (a simple path), the {@code ELProperty}
+ * is writeable for {@code Duke} when all of the following are true: {@code Duke} defines a Java Beans getter for
+ * {@code mother}, {@code Duke's mother} defines a Java Beans setter for
+ * {@code firstName}, {@code Duke} is {@code non-null}, {@code Duke's mother}
+ * is {@code non-null}. The {@code ELProperty} is therefore unreadable when
+ * any of the following is true: {@code Duke} does not define a Java Beans
+ * getter for {@code mother}, {@code Duke's mother} does not define a Java
+ * Beans setter for {@code firstName}, {@code Duke} is {@code null},
+ * {@code Duke's mother} is {@code null}. The second and third examples above
+ * both represent read-only ELExpressions and are therefore unwritable.
+ * <p>
+ * In addition to working on Java Beans properties, any object in the paths
+ * can be an instance of {@code Map}. In this case, the {@code Map's get}
+ * method is used with the property name as the getter, and the
+ * {@code Map's put} method is used with the property name as the setter.
+ * {@code ELProperty} can only respond to changes in {@code Maps}
+ * if they are instances of {@link org.jdesktop.observablecollections.ObservableMap}.
+ * <p>
+ * Some methods in this class document that they can throw
+ * {@code PropertyResolutionException} if an exception occurs while trying
+ * to evaluate the expression. The throwing of this exception represents an abnormal
+ * condition and if listeners are installed for the given source object,
+ * leaves the {@code ELProperty} in an inconsistent state for that source object.
+ * An {@code ELProperty} should not be used again for that same source object
+ * after such an exception without first removing all listeners associated with
+ * the {@code ELProperty} for that source object.
+ *
+ * @param <S> the type of source object that this {@code ELProperty} operates on
+ * @param <V> the type of value that this {@code ELProperty} represents
+ *
+ * @author Shannon Hickey
+ * @author Scott Violet
+ */
+public final class ELProperty<S, V> extends PropertyHelper<S, V> {
+
+    private Property<S, ?> baseProperty;
+    private final ValueExpression expression;
+    private final ELContext context = new TempELContext();
+    private IdentityHashMap<S, SourceEntry> map = new IdentityHashMap<S, SourceEntry>();
+    private static final Object NOREAD = new Object();
+
+    private final class SourceEntry implements PropertyChangeListener,
+                                               ObservableMapListener,
+                                               PropertyStateListener {
+
+        private S source;
+        private Object cachedBean;
+        private Object cachedValue;
+        private boolean cachedIsWriteable;
+        private Class<?> cachedWriteType;
+        private boolean ignoreChange;
+        private Set<RegisteredListener> registeredListeners;
+        private Set<RegisteredListener> lastRegisteredListeners;
+
+        private SourceEntry(S source) {
+            this.source = source;
+
+            if (baseProperty != null) {
+                baseProperty.addPropertyStateListener(source, this);
+            }
+
+            registeredListeners = new HashSet<RegisteredListener>(1);
+            updateCachedBean();
+            updateCache();
+        }
+
+        private void cleanup() {
+            for (RegisteredListener rl : registeredListeners) {
+                unregisterListener(rl, this);
+            }
+
+            if (baseProperty != null) {
+                baseProperty.removePropertyStateListener(source, this);
+            }
+
+            cachedBean = null;
+            registeredListeners = null;
+            cachedValue = null;
+        }
+
+        private boolean cachedIsReadable() {
+            return cachedValue != NOREAD;
+        }
+
+        private void updateCachedBean() {
+            cachedBean = getBeanFromSource(source, true);
+        }
+
+        private void updateCache() {
+            lastRegisteredListeners = registeredListeners;
+            registeredListeners = new HashSet<RegisteredListener>(lastRegisteredListeners.size());
+            List<ResolvedProperty> resolvedProperties = null;
+
+            try {
+                expression.setSource(getBeanFromSource(source, true));
+                Expression.Result result = expression.getResult(context, true);
+                
+                if (result.getType() == Expression.Result.Type.UNRESOLVABLE) {
+                    log("updateCache()", "expression is unresolvable");
+                    cachedValue = NOREAD;
+                    cachedIsWriteable = false;
+                    cachedWriteType = null;
+                } else {
+                    cachedValue = result.getResult();
+                    cachedIsWriteable = !expression.isReadOnly(context);
+                    cachedWriteType = cachedIsWriteable ? expression.getType(context) : null;
+                }
+
+                resolvedProperties = result.getResolvedProperties();
+            } catch (ELException ele) {
+                throw new PropertyResolutionException("Error evaluating EL expression " + expression + " on " + source, ele);
+            } finally {
+                expression.setSource(null);
+            }
+
+            for (ResolvedProperty prop : resolvedProperties) {
+                registerListener(prop, this);
+            }
+
+            // Uninstall all listeners that are no longer along the path.
+            for (RegisteredListener listener : lastRegisteredListeners) {
+                unregisterListener(listener, this);
+            }
+
+            lastRegisteredListeners = null;
+        }
+
+        // flag -1 - validate all
+        // flag  0 - source property changed value or readability
+        // flag  1 - something else changed
+        private void validateCache(int flag) {
+
+/* In the future, this debugging code can be enabled via a flag */
+            
+/*
+            if (flag != 0 && getBeanFromSource(source, false) != cachedBean) {
+                log("validateCache()", "concurrent modification");
+            }
+
+            if (flag != 1) {
+                try {
+                    expression.setSource(getBeanFromSource(source, true));
+                    Expression.Result result = expression.getResult(context, false);
+
+                    Object currValue;
+                    boolean currIsWriteable;
+                    Class<?> currWriteType;
+
+                    if (result.getType() == Expression.Result.Type.UNRESOLVABLE) {
+                        currValue = NOREAD;
+                        currIsWriteable = false;
+                        currWriteType = null;
+                    } else {
+                        currValue = result.getResult();
+                        currIsWriteable = !expression.isReadOnly(context);
+                        currWriteType = currIsWriteable ? expression.getType(context) : null;
+                    }
+
+                    if (!match(currValue, cachedValue) || currIsWriteable != cachedIsWriteable || currWriteType != cachedWriteType) {
+                        log("validateCache()", "concurrent modification");
+                    }
+                } catch (ELException ele) {
+                    throw new PropertyResolutionException("Error evaluating EL expression " + expression + " on " + source, ele);
+                } finally {
+                    expression.setSource(null);
+                }
+            }
+ */
+        }
+
+        public void propertyStateChanged(PropertyStateEvent pe) {
+            if (!pe.getValueChanged()) {
+                return;
+            }
+
+            validateCache(0);
+            Object oldValue = cachedValue;
+            boolean wasWriteable = cachedIsWriteable;
+            updateCachedBean();
+            updateCache();
+            notifyListeners(wasWriteable, oldValue, this);
+        }
+
+        private void processSourceChanged() {
+            validateCache(1);
+
+            boolean wasWriteable = cachedIsWriteable;
+            Object oldValue = cachedValue;
+
+            updateCache();
+            notifyListeners(wasWriteable, oldValue, this);
+        }
+
+        private void sourceChanged(Object source, String property) {
+            if (ignoreChange) {
+                return;
+            }
+
+            if (property != null) {
+                property = property.intern();
+            }
+
+            for (RegisteredListener rl : registeredListeners) {
+                if (rl.getSource() == source && (property == null || rl.getProperty() == property)) {
+                    processSourceChanged();
+                    break;
+                }
+            }
+        }
+
+        public void propertyChange(PropertyChangeEvent e) {
+            sourceChanged(e.getSource(), e.getPropertyName());
+        }
+
+        public void mapKeyValueChanged(ObservableMap map, Object key, Object lastValue) {
+            if (key instanceof String) {
+                sourceChanged(map, (String)key);
+            }
+        }
+
+        public void mapKeyAdded(ObservableMap map, Object key) {
+            if (key instanceof String) {
+                sourceChanged(map, (String)key);
+            }
+        }
+
+        public void mapKeyRemoved(ObservableMap map, Object key, Object value) {
+            if (key instanceof String) {
+                sourceChanged(map, (String)key);
+            }
+        }
+    }
+
+    /**
+     * Creates an instance of {@code ELProperty} for the given expression.
+     *
+     * @param expression the expression
+     * @return an instance of {@code ELProperty} for the given expression
+     * @throws IllegalArgumentException if the path is null or empty
+     * @throws PropertyResolutionException if there's a problem with the expression
+     */
+    public static final <S, V> ELProperty<S, V> create(String expression) {
+        return new ELProperty<S, V>(null, expression);
+    }
+
+    /**
+     * Creates an instance of {@code ELProperty} for the given base property
+     * and expression. The expression is relative to the value of the base property.
+     *
+     * @param baseProperty the base property
+     * @param expression the expression
+     * @return an instance of {@code ELProperty} for the given base property and expression
+     * @throws IllegalArgumentException if the path is null or empty
+     * @throws PropertyResolutionException if there's a problem with the expression
+     */
+    public static final <S, V> ELProperty<S, V> create(Property<S, ?> baseProperty, String expression) {
+        return new ELProperty<S, V>(baseProperty, expression);
+    }
+
+    /**
+     * @throws IllegalArgumentException for empty or {@code null} expression.
+     */
+    private ELProperty(Property<S, ?> baseProperty, String expression) {
+        if (expression == null || expression.length() == 0) {
+            throw new IllegalArgumentException("expression must be non-null and non-empty");
+        }
+
+        try {
+            this.expression = new ExpressionFactoryImpl().createValueExpression(context, expression, Object.class);
+        } catch (ELException ele) {
+            throw new PropertyResolutionException("Error creating EL expression " + expression, ele);
+        }
+
+        this.baseProperty = baseProperty;
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * See the class level documentation for the definition of <a href="#WRITEABILITY">writeability</a>.
+     *
+     * @throws UnsupportedOperationException {@inheritDoc}
+     * @throws PropertyResolutionException if an exception occurs while evaluating the expression
+     * @see #setValue
+     * @see #isWriteable
+     */
+    public Class<? extends V> getWriteType(S source) {
+        SourceEntry entry = map.get(source);
+
+        if (entry != null) {
+            entry.validateCache(-1);
+ 
+            if (!entry.cachedIsWriteable) {
+                throw new UnsupportedOperationException("Unwriteable");
+            }
+ 
+            return (Class<? extends V>)entry.cachedWriteType;
+        }
+
+        try {
+            expression.setSource(getBeanFromSource(source, true));
+            Expression.Result result = expression.getResult(context, false);
+
+            if (result.getType() == Expression.Result.Type.UNRESOLVABLE) {
+                log("getWriteType()", "expression is unresolvable");
+                throw new UnsupportedOperationException("Unwriteable");
+            }
+
+            if (expression.isReadOnly(context)) {
+                log("getWriteType()", "property is unwriteable");
+                throw new UnsupportedOperationException("Unwriteable");
+            }
+
+            return (Class<? extends V>)expression.getType(context);
+        } catch (ELException ele) {
+            throw new PropertyResolutionException("Error evaluating EL expression " + expression + " on " + source, ele);
+        } finally {
+            expression.setSource(null);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * See the class level documentation for the definition of <a href="#READABILITY">readability</a>.
+     *
+     * @throws UnsupportedOperationException {@inheritDoc}
+     * @throws PropertyResolutionException if an exception occurs while evaluating the expression
+     * @see #isReadable
+     */
+    public V getValue(S source) {
+        SourceEntry entry = map.get(source);
+
+        if (entry != null) {
+            entry.validateCache(-1);
+ 
+            if (entry.cachedValue == NOREAD) {
+                throw new UnsupportedOperationException("Unreadable");
+            }
+ 
+            return (V)entry.cachedValue;
+        }
+
+        try {
+            expression.setSource(getBeanFromSource(source, true));
+            Expression.Result result = expression.getResult(context, false);
+
+            if (result.getType() == Expression.Result.Type.UNRESOLVABLE) {
+                log("getValue()", "expression is unresolvable");
+                throw new UnsupportedOperationException("Unreadable");
+            }
+
+            return (V)result.getResult();
+        } catch (ELException ele) {
+            throw new PropertyResolutionException("Error evaluating EL expression " + expression + " on " + source, ele);
+        } finally {
+            expression.setSource(null);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * See the class level documentation for the definition of <a href="#WRITEABILITY">writeability</a>.
+     *
+     * @throws UnsupportedOperationException {@inheritDoc}
+     * @throws PropertyResolutionException if an exception occurs while evaluating the expression
+     * @see #isWriteable
+     * @see #getWriteType
+     */
+    public void setValue(S source, V value) {
+        SourceEntry entry = map.get(source);
+
+        if (entry != null) {
+            entry.validateCache(-1);
+ 
+            if (!entry.cachedIsWriteable) {
+                throw new UnsupportedOperationException("Unwritable");
+            }
+
+            try {
+                entry.ignoreChange = true;
+                expression.setSource(getBeanFromSource(source, false));
+                expression.setValue(context, value);
+            } catch (ELException ele) {
+                throw new PropertyResolutionException("Error evaluating EL expression " + expression + " on " + source, ele);
+            } finally {
+                entry.ignoreChange = false;
+                expression.setSource(null);
+            }
+ 
+            Object oldValue = entry.cachedValue;
+            // PENDING(shannonh) - too heavyweight; should just update cached value
+            entry.updateCache();
+            notifyListeners(entry.cachedIsWriteable, oldValue, entry);
+
+            return;
+        }
+
+        try {
+            expression.setSource(getBeanFromSource(source, true));
+            Expression.Result result = expression.getResult(context, false);
+
+            if (result.getType() == Expression.Result.Type.UNRESOLVABLE) {
+                log("setValue()", "expression is unresolvable");
+                throw new UnsupportedOperationException("Unwriteable");
+            }
+
+            if (expression.isReadOnly(context)) {
+                log("setValue()", "property is unwriteable");
+                throw new UnsupportedOperationException("Unwriteable");
+            }
+
+            expression.setValue(context, value);
+        } catch (ELException ele) {
+            throw new PropertyResolutionException("Error evaluating EL expression " + expression + " on " + source, ele);
+        } finally {
+            expression.setSource(null);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * See the class level documentation for the definition of <a href="#READABILITY">readability</a>.
+     *
+     * @throws UnsupportedOperationException {@inheritDoc}
+     * @throws PropertyResolutionException if an exception occurs while evaluating the expression
+     * @see #isWriteable
+     */
+    public boolean isReadable(S source) {
+        SourceEntry entry = map.get(source);
+
+        if (entry != null) {
+            entry.validateCache(-1);
+            return entry.cachedIsReadable();
+        }
+
+        try {
+            expression.setSource(getBeanFromSource(source, true));
+            Expression.Result result = expression.getResult(context, false);
+
+            if (result.getType() == Expression.Result.Type.UNRESOLVABLE) {
+                log("isReadable()", "expression is unresolvable");
+                return false;
+            }
+            
+            return true;
+        } catch (ELException ele) {
+            throw new PropertyResolutionException("Error evaluating EL expression " + expression + " on " + source, ele);
+        } finally {
+            expression.setSource(null);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * See the class level documentation for the definition of <a href="#WRITEABILITY">writeability</a>.
+     *
+     * @throws UnsupportedOperationException {@inheritDoc}
+     * @throws PropertyResolutionException if an exception occurs while evaluating the expression
+     * @see #isReadable
+     */
+    public boolean isWriteable(S source) {
+        SourceEntry entry = map.get(source);
+
+        if (entry != null) {
+            entry.validateCache(-1);
+            return entry.cachedIsWriteable;
+        }
+        
+        try {
+            expression.setSource(getBeanFromSource(source, true));
+            Expression.Result result = expression.getResult(context, false);
+
+            if (result.getType() == Expression.Result.Type.UNRESOLVABLE) {
+                log("isWriteable()", "expression is unresolvable");
+                return false;
+            }
+
+            if (expression.isReadOnly(context)) {
+                log("isWriteable()", "property is unwriteable");
+                return false;
+            }
+
+            return true;
+        } catch (ELException ele) {
+            throw new PropertyResolutionException("Error evaluating EL expression " + expression + " on " + source, ele);
+        } finally {
+            expression.setSource(null);
+        }
+    }
+
+    private Object getBeanFromSource(S source, boolean logErrors) {
+        if (baseProperty == null) {
+            if (source == null) {
+                if (logErrors) {
+                    log("getBeanFromSource()", "source is null");
+                }
+            }
+
+            return source;
+        }
+
+        if (!baseProperty.isReadable(source)) {
+            if (logErrors) {
+                log("getBeanFromSource()", "unreadable source property");
+            }
+            return NOREAD;
+        }
+
+        Object bean = baseProperty.getValue(source);
+        if (bean == null) {
+            if (logErrors) {
+                log("getBeanFromSource()", "source property returned null");
+            }
+            return null;
+        }
+        
+        return bean;
+    }
+
+    protected final void listeningStarted(S source) {
+        SourceEntry entry = map.get(source);
+        if (entry == null) {
+            entry = new SourceEntry(source);
+            map.put(source, entry);
+        }
+    }
+
+    protected final void listeningStopped(S source) {
+        SourceEntry entry = map.remove(source);
+        if (entry != null) {
+            entry.cleanup();
+        }
+    }
+
+    private static boolean didValueChange(Object oldValue, Object newValue) {
+        return oldValue == null || newValue == null || !oldValue.equals(newValue);
+    }
+
+    private void notifyListeners(boolean wasWriteable, Object oldValue, SourceEntry entry) {
+        PropertyStateListener[] listeners = getPropertyStateListeners(entry.source);
+
+        if (listeners == null || listeners.length == 0) {
+            return;
+        }
+
+        oldValue = toUNREADABLE(oldValue);
+        Object newValue = toUNREADABLE(entry.cachedValue);
+        boolean valueChanged = didValueChange(oldValue, newValue);
+        boolean writeableChanged = (wasWriteable != entry.cachedIsWriteable);
+
+        if (!valueChanged && !writeableChanged) {
+            return;
+        }
+
+        PropertyStateEvent pse = new PropertyStateEvent(this,
+                                                        entry.source,
+                                                        valueChanged,
+                                                        oldValue,
+                                                        newValue,
+                                                        writeableChanged,
+                                                        entry.cachedIsWriteable);
+
+        this.firePropertyStateChange(pse);
+    }
+
+    /**
+     * Returns a string representation of the {@code ELProperty}. This
+     * method is intended to be used for debugging purposes only, and
+     * the content and format of the returned string may vary between
+     * implementations. The returned string may be empty but may not
+     * be {@code null}.
+     *
+     * @return a string representation of this {@code ELProperty}
+     */
+    public String toString() {
+        return getClass().getName() + "[" + expression + "]";
+    }
+
+    /**
+     * @throws PropertyResolutionException
+     */
+    private static BeanInfo getBeanInfo(Object object) {
+        assert object != null;
+
+        try {
+            return Introspector.getBeanInfo(object.getClass(), Introspector.IGNORE_ALL_BEANINFO);
+        } catch (IntrospectionException ie) {
+            throw new PropertyResolutionException("Exception while introspecting " + object.getClass().getName(), ie);
+        }
+    }
+
+    private static EventSetDescriptor getEventSetDescriptor(Object object) {
+        assert object != null;
+        
+        EventSetDescriptor[] eds = getBeanInfo(object).getEventSetDescriptors();
+        for (EventSetDescriptor ed : eds) {
+            if (ed.getListenerType() == PropertyChangeListener.class) {
+                return ed;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * @throws PropertyResolutionException
+     */
+    private static Object invokeMethod(Method method, Object object, Object... args) {
+        Exception reason = null;
+
+        try {
+            return method.invoke(object, args);
+        } catch (IllegalArgumentException ex) {
+            reason = ex;
+        } catch (IllegalAccessException ex) {
+            reason = ex;
+        } catch (InvocationTargetException ex) {
+            reason = ex;
+        }
+
+        throw new PropertyResolutionException("Exception invoking method " + method + " on " + object, reason);
+    }
+
+    private static Object toUNREADABLE(Object src) {
+        return src == NOREAD ? UNREADABLE : src;
+    }
+
+    private void registerListener(ResolvedProperty resolved, SourceEntry entry) {
+        Object source = resolved.getSource();
+        Object property = resolved.getProperty();
+        if (source != null && property instanceof String) {
+            String sProp = (String)property;
+
+            if (source instanceof ObservableMap) {
+                RegisteredListener rl = new RegisteredListener(source, sProp);
+
+                if (!entry.registeredListeners.contains(rl)) {
+                    if (!entry.lastRegisteredListeners.remove(rl)) {
+                        ((ObservableMap)source).addObservableMapListener(entry);
+                    }
+                    
+                    entry.registeredListeners.add(rl);
+                }
+            } else if (!(source instanceof Map)) {
+                source = getAdapter(source, sProp);
+
+                RegisteredListener rl = new RegisteredListener(source, sProp);
+
+                if (!entry.registeredListeners.contains(rl)) {
+                    if (!entry.lastRegisteredListeners.remove(rl)) {
+                        addPropertyChangeListener(source, entry);
+                    }
+                    
+                    entry.registeredListeners.add(rl);
+                }
+            }
+        }
+    }
+
+    private void unregisterListener(RegisteredListener rl, SourceEntry entry) {
+        Object source = rl.getSource();
+        if (source instanceof ObservableMap) {
+            ((ObservableMap)source).removeObservableMapListener(entry);
+        } else if (!(source instanceof Map)) {
+            removePropertyChangeListener(source, entry);
+        }
+    }
+
+    /**
+     * @throws PropertyResolutionException
+     */
+    private static void addPropertyChangeListener(Object object, PropertyChangeListener listener) {
+        EventSetDescriptor ed = getEventSetDescriptor(object);
+        Method addPCMethod = null;
+
+        if (ed == null || (addPCMethod = ed.getAddListenerMethod()) == null) {
+            log("addPropertyChangeListener()", "can't add listener");
+            return;
+        }
+
+        invokeMethod(addPCMethod, object, listener);
+    }
+
+    /**
+     * @throws PropertyResolutionException
+     */
+    private static void removePropertyChangeListener(Object object, PropertyChangeListener listener) {
+        EventSetDescriptor ed = getEventSetDescriptor(object);
+        Method removePCMethod = null;
+
+        if (ed == null || (removePCMethod = ed.getRemoveListenerMethod()) == null) {
+            log("removePropertyChangeListener()", "can't remove listener from source");
+            return;
+        }
+        
+        invokeMethod(removePCMethod, object, listener);
+    }
+
+    private static boolean wrapsLiteral(Object o) {
+        assert o != null;
+
+        return o instanceof String ||
+               o instanceof Byte ||
+               o instanceof Character ||
+               o instanceof Boolean ||
+               o instanceof Short ||
+               o instanceof Integer ||
+               o instanceof Long ||
+               o instanceof Float ||
+               o instanceof Double;
+    }
+
+    // need special match method because when using reflection
+    // to get a primitive value, the value is always wrapped in
+    // a new object
+    private static boolean match(Object a, Object b) {
+        if (a == b) {
+            return true;
+        }
+
+        if (a == null) {
+            return false;
+        }
+
+        if (wrapsLiteral(a)) {
+            return a.equals(b);
+        }
+
+        return false;
+    }
+
+    private Object getAdapter(Object o, String property) {
+        Object adapter = null;
+        adapter = BeanAdapterFactory.getAdapter(o, property);
+        return adapter == null ? o : adapter;
+    }
+    
+    private static final boolean LOG = false;
+
+    private static void log(String method, String message) {
+        if (LOG) {
+            System.err.println("LOG: " + method + ": " + message);
+        }
+    }
+
+    private static final class RegisteredListener {
+        private final Object source;
+        private final String property;
+        
+        RegisteredListener(Object source) {
+            this(source, null);
+        }
+        
+        RegisteredListener(Object source, String property) {
+            this.source = source;
+            if (property != null) {
+                property = property.intern();
+            }
+            this.property = property;
+        }
+        
+        public Object getSource() {
+            return source;
+        }
+        
+        public String getProperty() {
+            return property;
+        }
+
+        public boolean equals(Object obj) {
+            if (obj == this) {
+                return true;
+            }
+            if (obj instanceof RegisteredListener) {
+                RegisteredListener orl = (RegisteredListener) obj;
+                return (orl.source == source && orl.property == property);
+            }
+            return false;
+        }
+
+        public int hashCode() {
+            int result = 17;
+            result = 37 * result + source.hashCode();
+            if (property != null) {
+                result = 37 * result + property.hashCode();
+            }
+            return result;
+        }
+
+        public String toString() {
+            return "RegisteredListener [" +
+                    " source=" + source +
+                    " property=" + property + 
+                    "]";
+        }
+    }
+
+}
diff --git a/src/org/jdesktop/beansbinding/ObjectProperty.java b/src/org/jdesktop/beansbinding/ObjectProperty.java
new file mode 100644
index 0000000..4dff44b
--- /dev/null
+++ b/src/org/jdesktop/beansbinding/ObjectProperty.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.beansbinding;
+
+import static org.jdesktop.beansbinding.PropertyStateEvent.UNREADABLE;
+
+/**
+ * An immutable, read-only, {@code Property} implementation whose {@code getValue}
+ * method returns the source object that it is given. This class is useful when
+ * you want to configure a {@code Binding} to use its source object directly,
+ * rather than some property of the source object. For example:
+ * <p>
+ * <pre><code>
+ *    new SomeBindingClass(sourceObject, ObjectProperty.create(), targetObject, targetProperty);
+ * </code></pre>
+ * <p>
+ * Explicitly using {@code ObjectProperty} isn't necessary when creating {@code Bindings}
+ * from this package or the {@code SwingBindings} package, as the set of static creation
+ * methods include versions that handle this for you.
+ *
+ * @param <S> the type of source object that this {@code Property} operates on
+ *            and therefore the type of value that it represents
+ *
+ * @author Shannon Hickey
+ */
+public final class ObjectProperty<S> extends Property<S, S> {
+
+    /**
+     * Creates an instance of {@code ObjectProperty}.
+     */
+    public static <S> ObjectProperty<S> create() {
+        return new ObjectProperty<S>();
+    }
+
+    private ObjectProperty() {}
+
+    /**
+     * Throws {@code UnsupportedOperationException}; {@code ObjectProperty} is never writeable.
+     *
+     * @param source {@inheritDoc}
+     * @return never returns; always throws {@code UnsupportedOperationException}; {@code ObjectProperty} is never writeable
+     * @throws UnsupportedOperationException always; {@code ObjectProperty} is never writeable
+     * @see #isWriteable
+     */
+    public Class<? extends S> getWriteType(S source) {
+        throw new UnsupportedOperationException("Unwriteable");
+    }
+
+    /**
+     * Returns the source object passed to the method.
+     *
+     * @return the value of the {@code source} argument
+     * @see #isReadable
+     */
+    public S getValue(S source) {
+        return source;
+    }
+
+    /**
+     * Throws {@code UnsupportedOperationException}; {@code ObjectProperty} is never writeable.
+     *
+     * @param source {@inheritDoc}
+     * @throws UnsupportedOperationException always; {@code ObjectProperty} is never writeable
+     * @see #isWriteable
+     * @see #getWriteType
+     */
+    public void setValue(S source, S value) {
+        throw new UnsupportedOperationException("Unwriteable");
+    }
+
+    /**
+     * Returns {@code true}; {@code ObjectProperty} is always readable.
+     *
+     * @return {@code true}; {@code ObjectPropert} is always readable
+     * @see #isWriteable
+     */
+    public boolean isReadable(Object source) {
+        return true;
+    }
+
+    /**
+     * Returns {@code false}; {@code ObjectProperty} is never writeable.
+     *
+     * @return {@code false}; {@code ObjectPropert} is never writeable
+     * @see #isReadable
+     */
+    public boolean isWriteable(Object source) {
+        return false;
+    }
+
+    /**
+     * Returns a string representation of the {@code ObjectProperty}. This
+     * method is intended to be used for debugging purposes only, and
+     * the content and format of the returned string may vary between
+     * implementations. The returned string may be empty but may not
+     * be {@code null}.
+     *
+     * @return a string representation of this {@code ObjectProperty}
+     */
+    public String toString() {
+        return getClass().getName();
+    }
+
+    /**
+     * Does nothing; the state of an {@code ObjectProperty} never changes so
+     * listeners aren't useful.
+     */
+    public void addPropertyStateListener(S source, PropertyStateListener listener) {}
+
+    /**
+     * Does nothing; the state of an {@code ObjectProperty} never changes so
+     * listeners aren't useful.
+     *
+     * @see #addPropertyStateListener
+     */
+    public void removePropertyStateListener(S source, PropertyStateListener listener) {}
+
+    /**
+     * Returns an empty array; the state of an {@code ObjectProperty} never changes
+     * so listeners aren't useful.
+     *
+     * @return an empty array
+     * @see #addPropertyStateListener
+     */
+    public PropertyStateListener[] getPropertyStateListeners(S source) {
+        return new PropertyStateListener[0];
+    }
+
+}
diff --git a/src/org/jdesktop/beansbinding/Property.java b/src/org/jdesktop/beansbinding/Property.java
new file mode 100644
index 0000000..5ee0c85
--- /dev/null
+++ b/src/org/jdesktop/beansbinding/Property.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.beansbinding;
+
+/**
+ * {@code Property} defines a uniform way to access the value of a property.
+ * A typical {@code Property} implemention allows you to create an immutable
+ * representation of a way to derive some property from a source object.
+ * As such, all methods of this class take a source object as an argument.
+ * <p>
+ * A {@code Property} implementation may, however, be designed such that the
+ * {@code Property} itself is a mutable thing that stores a property value.
+ * In such a case, the {@code Property} implementation may ignore the source
+ * object. {@code Property} implementations should clearly document their
+ * behavior in this regard.
+ * <p>
+ * You can listen for changes in the state of a {@code Property} by
+ * registering {@code PropertyStateListeners} on the {@code Property}.
+ *
+ * @param <S> the type of source object that this {@code Property} operates on
+ * @param <V> the type of value that this {@code Property} represents
+ *
+ * @author Shannon Hickey
+ */
+public abstract class Property<S, V> {
+
+    /**
+     * Returns the type of object that is suitable for setting as the value
+     * of this {@code Property} by calls to {@code setValue}.
+     *
+     * @param source the source object on which to operate
+     * @return the type of object suitable for setting as the value
+     * @throws UnsupportedOperationException if the {@code Property} is not
+     *         writeable for the given source
+     * @see #setValue
+     * @see #isWriteable
+     */
+    public abstract Class<? extends V> getWriteType(S source);
+
+    /**
+     * Returns the value of this {@code Property} for the given source.
+     *
+     * @param source the source object on which to operate
+     * @return the value of this {@code Property} for the given source
+     * @throws UnsupportedOperationException if the {@code Property} is not
+     *         readable for the given source
+     * @see #isReadable
+     */
+    public abstract V getValue(S source);
+
+    /**
+     * Sets the value of this {@code Property} for the given source.
+     *
+     * @param source the source object on which to operate
+     * @param value the new value for the {@code Property}
+     * @throws UnsupportedOperationException if the {@code Property} is not
+     *         writeable for the given source
+     * @see #isWriteable
+     * @see #getWriteType
+     */
+    public abstract void setValue(S source, V value);
+
+    /**
+     * Returns whether or not the {@code Property} is readable for the given source.
+     *
+     * @param source the source object on which to operate
+     * @return whether or not the {@code Property} is readable for the given source.
+     * @see #isWriteable
+     */
+    public abstract boolean isReadable(S source);
+
+    /**
+     * Returns whether or not the {@code Property} is writeable for the given source.
+     *
+     * @param source the source object on which to operate
+     * @return whether or not the {@code Property} is writeable for the given source.
+     * @see #isReadable
+     */
+    public abstract boolean isWriteable(S source);
+
+    /**
+     * Adds a {@code PropertyStateListener} to be notified when the state of the
+     * {@code Property} changes with respect to the given source. Does nothing if
+     * the listener is {@code null}. If a listener is added more than once,
+     * notifications are sent to that listener once for every time that it has
+     * been added. The ordering of listener notification is unspecified.
+     *
+     * @param source the source object on which to operate
+     * @param listener the listener to be notified
+     */
+    public abstract void addPropertyStateListener(S source, PropertyStateListener listener);
+
+    /**
+     * Removes a {@code PropertyStateListener} for the given source. Does
+     * nothing if the listener is {@code null} or is not one of those registered
+     * for this source object. If the listener being removed was registered more
+     * than once, only one occurrence of the listener is removed from the list of
+     * listeners. The ordering of listener notification is unspecified.
+     *
+     * @param source the source object on which to operate
+     * @param listener the listener to be removed
+     * @see #addPropertyStateListener
+     */
+    public abstract void removePropertyStateListener(S source, PropertyStateListener listener);
+
+    /**
+     * Returns an arry containing the listeners registered for the given source.
+     * Order is undefined. Returns an empty array if there are no listeners.
+     *
+     * @param source the source object on which to operate
+     * @return the set of listeners registered for the given source
+     * @see #addPropertyStateListener
+     */
+    public abstract PropertyStateListener[] getPropertyStateListeners(S source);
+
+}
diff --git a/src/org/jdesktop/beansbinding/PropertyHelper.java b/src/org/jdesktop/beansbinding/PropertyHelper.java
new file mode 100644
index 0000000..eb552f5
--- /dev/null
+++ b/src/org/jdesktop/beansbinding/PropertyHelper.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.beansbinding;
+
+import java.util.*;
+
+/**
+ * An abstract subclass of {@code Property} that helps with the management of
+ * {@code PropertyStateListeners} by implementing the methods for adding, removing,
+ * and getting listeners. {@code PropertyHelper} can be constructed
+ * to manage listeners for multiple source objects, or to ignore the source
+ * object argument when dealing with listeners and associate them directly with
+ * the {@code PropertyHelper} instance itself. This makes {@code PropertyHelper}
+ * useful as a base for both property types described in the documentation for
+ * {@code Property}.
+ * <p>
+ * {@code PropertyHelper} also provides, by way of the protected methods
+ * {@link #listeningStarted} and {@link #listeningStopped} a hook for subclasses
+ * to know when it's time to start tracking changes to a particular source object.
+ *
+ * @param <S> the type of source object that this {@code Property} operates on
+ * @param <V> the type of value that this {@code Property} represents
+ *
+ * @author Shannon Hickey
+ */
+public abstract class PropertyHelper<S, V> extends Property<S, V> {
+
+    private final boolean ignoresSource;
+    private Object listeners;
+
+    /**
+     * Create a {@code PropertyHelper} that manages listeners for multiple
+     * source objects.
+     */
+    public PropertyHelper() {
+        this(false);
+    }
+
+    /**
+     * Create a {@code PropertyHelper}, specifying whether it manages
+     * listeners for multiple source objects, or ignores the source object
+     * argument when dealing with listeners
+     *
+     * @param ignoresSource whether or not the source argument is ignored
+     *        when dealing with listeners
+     */
+    public PropertyHelper(boolean ignoresSource) {
+        this.ignoresSource = ignoresSource;
+    }
+
+    private List<PropertyStateListener> getListeners(S source, boolean create) {
+        if (ignoresSource) {
+            List<PropertyStateListener> list = (List<PropertyStateListener>)listeners;
+
+            if (list == null && create) {
+                list = new ArrayList<PropertyStateListener>();
+                listeners = list;
+            }
+
+            return list;
+        }
+
+        IdentityHashMap<S, List<PropertyStateListener>> map = (IdentityHashMap<S, List<PropertyStateListener>>)listeners;
+
+        if (map == null) {
+            if (create) {
+                map = new IdentityHashMap<S, List<PropertyStateListener>>();
+                listeners = map;
+            } else {
+                return null;
+            }
+        }
+
+        List<PropertyStateListener> list = map.get(source);
+        if (list == null && create) {
+            list = new ArrayList<PropertyStateListener>();
+            map.put(source, list);
+        }
+
+        return list;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws UnsupportedOperationException {@inheritDoc}
+     */
+    public abstract Class<? extends V> getWriteType(S source);
+
+    /**
+     * {@inheritDoc}
+     * @throws UnsupportedOperationException {@inheritDoc}
+     */
+    public abstract V getValue(S source);
+
+    /**
+     * {@inheritDoc}
+     * @throws UnsupportedOperationException {@inheritDoc}
+     */
+    public abstract void setValue(S source, V value);
+
+    /**
+     * {@inheritDoc}
+     * @throws UnsupportedOperationException {@inheritDoc}
+     */
+    public abstract boolean isReadable(S source);
+
+    /**
+     * {@inheritDoc}
+     * @throws UnsupportedOperationException {@inheritDoc}
+     */
+    public abstract boolean isWriteable(S source);
+
+    /**
+     * Called when this {@code PropertyHelper} changes from having
+     * no listeners installed for the given source object to
+     * having listeners installed for the given source object. This
+     * is the ideal time for subclasses to install any listeners needed
+     * to track change on the source object.
+     *
+     * @see #listeningStopped
+     */
+    protected void listeningStarted(S source) {
+    }
+
+    /**
+     * Called when this {@code PropertyHelper} changes from having
+     * listeners installed for the given source object to
+     * having no listeners installed for the given source object. This
+     * is the ideal time for subclasses to remove any listeners that
+     * they've installed to track changes on the source object.
+     *
+     * @see #listeningStopped
+     */
+    protected void listeningStopped(S source) {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public final void addPropertyStateListener(S source, PropertyStateListener listener) {
+        if (listener == null) {
+            return;
+        }
+
+        List<PropertyStateListener> listeners = getListeners(source, true);
+        boolean wasListening = (listeners.size() != 0);
+        listeners.add(listener);
+
+        if (!wasListening) {
+            listeningStarted(ignoresSource ? null : source);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public final void removePropertyStateListener(S source, PropertyStateListener listener) {
+        if (listener == null) {
+            return;
+        }
+
+        List<PropertyStateListener> listeners = getListeners(source, false);
+
+        if (listeners == null) {
+            return;
+        }
+
+        boolean wasListening = (listeners.size() != 0);
+
+        listeners.remove(listener);
+
+        if (wasListening && listeners.size() == 0) {
+            listeningStopped(ignoresSource ? null : source);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public final PropertyStateListener[] getPropertyStateListeners(S source) {
+         List<PropertyStateListener> listeners = getListeners(source, false);
+
+        if (listeners == null) {
+            return new PropertyStateListener[0];
+        }
+
+        PropertyStateListener[] ret = new PropertyStateListener[listeners.size()];
+        ret = listeners.toArray(ret);
+        return ret;
+    }
+
+    /**
+     * Notify listeners that the state of this property has changed, as
+     * characterized by the given {@code PropertyStateEvent}. If this
+     * {@code PropertyHelper} is managing listeners for multiple sources, only
+     * the listeners associated with the object returned by the
+     * {@code PropertyStateEvent's getSourceObject()} method are notified.
+     *
+     * @param pse the {@code PropertyStateEvent} characterizing the state change
+     */
+    protected final void firePropertyStateChange(PropertyStateEvent pse) {
+        List<PropertyStateListener> listeners = getListeners((S)pse.getSourceObject(), false);
+
+        if (listeners == null) {
+            return;
+        }
+
+        for (PropertyStateListener listener : listeners) {
+            listener.propertyStateChanged(pse);
+        }
+    }
+
+    /**
+     * Returns whether or not there are any {@code PropertyStateListeners}
+     * installed for the given source object.
+     *
+     * @param source the source object of interest
+     * @return whether or not there are any {@code PropertyStateListeners}
+     *         installed for the given source object
+     */
+    public final boolean isListening(S source) {
+         List<PropertyStateListener> listeners = getListeners(source, false);
+         return listeners != null && listeners.size() != 0;
+    }
+
+}
diff --git a/src/org/jdesktop/beansbinding/PropertyPath.java b/src/org/jdesktop/beansbinding/PropertyPath.java
new file mode 100644
index 0000000..c4eb819
--- /dev/null
+++ b/src/org/jdesktop/beansbinding/PropertyPath.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.beansbinding;
+
+import java.util.StringTokenizer;
+import java.util.ArrayList;
+
+/**
+ * @author Shannon Hickey
+ * @author Scott Violet
+ */
+abstract class PropertyPath {
+
+    private PropertyPath() {}
+
+    public abstract int length();
+
+    // throws ArrayIndexOutBoundsException if not valid
+    public abstract String get(int index);
+
+    public String getLast() {
+        return get(length() - 1);
+    }
+
+    public abstract String toString();
+
+    public static PropertyPath createPropertyPath(String path) {
+        if (path == null) {
+            throw new IllegalArgumentException("path must be non-null");
+        }
+
+        StringTokenizer tokenizer = new StringTokenizer(path, ".");
+        ArrayList<String> list = new ArrayList<String>();
+        while (tokenizer.hasMoreTokens()) {
+            list.add(tokenizer.nextToken());
+        }
+
+        int size = list.size();
+
+        if (size == 0) {
+            throw new IllegalArgumentException("path must be non-empty");
+        } else if (list.size() == 1) {
+            return new SinglePropertyPath(list.get(0));
+        } else {
+            String[] multi = new String[list.size()];
+            return new MultiPropertyPath(list.toArray(multi));
+        }
+    }
+
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+
+        if (o instanceof PropertyPath) {
+            PropertyPath oPath = (PropertyPath)o;
+
+            int length = length();
+
+            if (length != oPath.length()) {
+                return false;
+            }
+
+            for (int i = 0; i < length; i++) {
+                if (!get(i).equals(oPath.get(i))) {
+                    return false;
+                }
+
+                return true;
+            }
+        }
+
+        return false;
+    }
+    
+    public int hashCode() {
+        int result = 17;
+        int length = length();
+
+        for (int i = 0; i < length; i++) {
+            result = 37 * result + get(i).hashCode();
+        }
+
+        return result;
+    }
+
+    static final class MultiPropertyPath extends PropertyPath {
+        private final String[] path;
+        
+        public MultiPropertyPath(String[] path) {
+            this.path = path;
+
+            for (int i = 0; i < path.length; i++) {
+                path[i] = path[i].intern();
+            }
+
+            assert (path.length > 0);
+        }
+
+        public int length() {
+            return path.length;
+        }
+
+        public String get(int index) {
+            return path[index];
+        }
+        
+        public String toString() {
+            StringBuilder builder = new StringBuilder();
+            builder.append(path[0]);
+            for (int i = 1; i < path.length; i++) {
+                builder.append('.');
+                builder.append(path[i]);
+            }
+            return builder.toString();
+        }
+    }
+    
+    
+    static final class SinglePropertyPath extends PropertyPath {
+        private final String path;
+        
+        public SinglePropertyPath(String path) {
+            this.path = path.intern();
+        }
+
+        public int length() {
+            return 1;
+        }
+
+        public String get(int index) {
+            if (index == 0) {
+                return path;
+            }
+
+            throw new ArrayIndexOutOfBoundsException();
+        }
+
+        public String getLast() {
+            return path;
+        }
+
+        public String toString() {
+            return path;
+        }
+    }
+}
diff --git a/src/org/jdesktop/beansbinding/PropertyResolutionException.java b/src/org/jdesktop/beansbinding/PropertyResolutionException.java
new file mode 100644
index 0000000..00e4fc1
--- /dev/null
+++ b/src/org/jdesktop/beansbinding/PropertyResolutionException.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.beansbinding;
+
+/**
+ * {@code PropertyResolutionExceptions} can be thrown at various points in
+ * the life cycle of a {@code Property}. Any time a {@code Property}
+ * encounters an exception in resolving a property, a
+ * {@code PropertyResolutionException} can be thrown. For example, if a
+ * {@code BeanProperty} encounters an exception while trying to resolve
+ * the "foo" property of an object via reflection, the exception is
+ * wrapped in a {@code PropertyResolutionException} and is re-thrown.
+ *
+ * @author Shannon Hickey
+ * @author Scott Violet
+ */
+public class PropertyResolutionException extends RuntimeException {
+
+    /**
+     * Creates a {@code PropertyResolutionException} with the given message.
+     *
+     * @param message the exception's message
+     */
+    public PropertyResolutionException(String message) {
+        super(message);
+    }
+
+    /**
+     * Creates a {@code PropertyResolutionException} with the given message
+     * and cause.
+     *
+     * @param message the exception's message
+     * @param reason the original exception that caused this exception
+     *        to be thrown
+     */
+    public PropertyResolutionException(String message, Exception reason) {
+        super(message, reason);
+    }
+
+}
diff --git a/src/org/jdesktop/beansbinding/PropertyStateEvent.java b/src/org/jdesktop/beansbinding/PropertyStateEvent.java
new file mode 100644
index 0000000..bdc225e
--- /dev/null
+++ b/src/org/jdesktop/beansbinding/PropertyStateEvent.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.beansbinding;
+
+import java.util.EventObject;
+
+/**
+ * An event characterizing a change in a {@code Property's} state for
+ * a particular source object.
+ *
+ * @see Property
+ * @see PropertyStateListener
+ *
+ * @author Shannon Hickey
+ */
+public class PropertyStateEvent extends EventObject {
+
+    /**
+     * Used to indicate that a particular value is unreadable.
+     */
+    public static final Object UNREADABLE = new StringBuffer("UNREADABLE");
+
+    private Object sourceObject;
+    private final boolean valueChanged;
+    private final Object oldValue;
+    private final Object newValue;
+    private final boolean writeableChanged;
+    private boolean isWriteable;
+
+    /**
+     * Creates an instance of {@code PropertyStateEvent} characterizing a
+     * change in a {@code Property's} state for a particular source object.
+     * <p>
+     * Note: To indicate a change in readability, specify {@code valueChanged}
+     * as {@code true} and reflect the readability status in the {@code oldValue}
+     * and {@code newValue} arguments.
+     *
+     * @param sourceProperty the {@code Property} whose state has changed
+     * @param sourceObject the source object for which the {@code Property's} state has changed
+     * @param valueChanged whether or not the {@code Property's} value has changed for the source object
+     * @param oldValue the old value of the {@code Property} for the source object,
+     *        or {@code UNREADABLE} if the {@code Property} was not previously readable for the source object
+     * @param newValue the new value of the {@code Property} for the source object,
+     *        or {@code UNREADABLE} if the {@code Property} is not currently readable for the source object
+     * @param writeableChanged whether or not the {@code Property's} writeability has changed for the source object
+     * @param isWriteable whether or not the {@code Property} is now writeable for the source object
+     * @throws IllegalArgumentException if neither the value or the writeability has changed
+     * @throws IllegalArgumentException if {@code valueChanged} is {@code true} and both
+     *         {@code oldValue} and {@code newValue} are {@code UNREADABLE}
+     */
+    public PropertyStateEvent(Property sourceProperty,
+                              Object sourceObject,
+                              boolean valueChanged,
+                              Object oldValue,
+                              Object newValue,
+                              boolean writeableChanged,
+                              boolean isWriteable) {
+
+        super(sourceProperty);
+
+        if (!writeableChanged && !valueChanged) {
+            throw new IllegalArgumentException("Nothing has changed");
+        }
+
+        if (valueChanged && oldValue == UNREADABLE && newValue == UNREADABLE) {
+            throw new IllegalArgumentException("Value can't change from UNREADABLE to UNREADABLE");
+        }
+
+        this.sourceObject = sourceObject;
+        this.valueChanged = valueChanged;
+        this.oldValue = oldValue;
+        this.newValue = newValue;
+        this.writeableChanged = writeableChanged;
+        this.isWriteable = isWriteable;
+    }
+
+    /**
+     * Returns the {@code Property} whose state has changed.
+     * The preferred way to access this value is via the
+     * {@link #getSourceProperty} method.
+     *
+     * @return the {@code Property} whose state has changed.
+     */
+    public final Object getSource() {
+        return super.getSource();
+    }
+
+    /**
+     * Returns the {@code Property} whose state has changed.
+     *
+     * @return the {@code Property} whose state has changed.
+     */
+    public final Property getSourceProperty() {
+        return (Property)getSource();
+    }
+
+    /**
+     * Returns the source object for which the {@code Property's} state has changed.
+     *
+     * @return the source object for which the {@code Property's} state has changed
+     */
+    public final Object getSourceObject() {
+        return sourceObject;
+    }
+
+    /**
+     * Returns whether or not the {@code Property's} value has changed for the source object.
+     *
+     * @return whether or not the {@code Property's} value has changed for the source object.
+     */
+    public final boolean getValueChanged() {
+        return valueChanged;
+    }
+
+    /**
+     * Returns the old value of the {@code Property} for the source object,
+     * or {@code UNREADABLE} if the {@code Property} was not previously readable for the
+     * source object.
+     * <p>
+     * Note: This method must only be called if {@code getValueChanged} returns
+     * {@code true}.
+     *
+     * @return the old value of the {@code Property} for the source object
+     *         or {@code UNREADABLE}
+     * @throws UnsupportedOperationException if the value hasn't changed
+     */
+    public final Object getOldValue() {
+        if (!valueChanged) {
+            throw new UnsupportedOperationException("value hasn't changed");
+        }
+
+        return oldValue;
+    }
+
+    /**
+     * Returns the new value of the {@code Property} for the source object,
+     * or {@code UNREADABLE} if the {@code Property} is not currently readable for the
+     * source object.
+     * <p>
+     * Note: This method must only be called if {@code getValueChanged} returns
+     * {@code true}.
+     *
+     * @return the new value of the {@code Property} for the source object
+     *         or {@code UNREADABLE}
+     * @throws UnsupportedOperationException if the value hasn't changed
+     */
+    public final Object getNewValue() {
+        if (!valueChanged) {
+            throw new UnsupportedOperationException("value hasn't changed");
+        }
+
+        return newValue;
+    }
+
+    /**
+     * Returns whether or not the {@code Property's} readability has changed for
+     * the source object. In particuler, this returns {@code true} if the value
+     * has changed and either the old value or new value is {@code UNREADABLE},
+     * and {@code false} otherwise.
+     *
+     * @return whether or not the {@code Property's} readability has changed for
+     * the source object.
+     */
+    public final boolean getReadableChanged() {
+        return valueChanged && oldValue != newValue && (oldValue == UNREADABLE || newValue == UNREADABLE);
+    }
+
+    /**
+     * Returns whether or not the {@code Property} is currently readable for
+     * the source object. In particular, this returns {@code true} if and only
+     * if the new value is not {@code UNREADABLE}.
+     * <p>
+     * Note: This method must only be called if {@code getReadableChanged} returns
+     * {@code true}.
+     *
+     * @return whether or not the {@code Property} is currently readable for
+     * the source object.
+     * @throws UnsupportedOperationException if the readability hasn't changed
+     */
+    public final boolean isReadable() {
+        if (!getReadableChanged()) {
+            throw new UnsupportedOperationException("readability hasn't changed");
+        }
+
+        return newValue != UNREADABLE;
+    }
+
+    /**
+     * Returns whether or not the {@code Property's} writeability has changed for
+     * the source object.
+     *
+     * @return whether or not the {@code Property's} writeability has changed for
+     * the source object.
+     */
+    public final boolean getWriteableChanged() {
+        return writeableChanged;
+    }
+
+    /**
+     * Returns whether or not the {@code Property} is currently writeable for
+     * the source object.
+     * <p>
+     * Note: This method must only be called if {@code getWriteableChanged} returns
+     * {@code true}.
+     *
+     * @return whether or not the {@code Property} is currently writeable for
+     * the source object.
+     * @throws UnsupportedOperationException if the writeability hasn't changed
+     */
+    public final boolean isWriteable() {
+        if (!writeableChanged) {
+            throw new UnsupportedOperationException("writeability hasn't changed");
+        }
+
+        return isWriteable;
+    }
+
+    /**
+     * Returns a string representation of the {@code PropertyStateEvent}. This
+     * method is intended to be used for debugging purposes only, and
+     * the content and format of the returned string may vary between
+     * implementations. The returned string may be empty but may not
+     * be {@code null}.
+     *
+     * @return a string representation of this {@code PropertyStateEvent}
+     */
+    public String toString() {
+        StringBuffer buffer = new StringBuffer(getClass().getName());
+
+        buffer.append(": Property ").append(getSourceProperty()).append(" changed on ").append(getSourceObject()).append(":\n");
+        
+        if (getValueChanged()) {
+            buffer.append("    value changed from ").append(getOldValue()).append(" to ").append(getNewValue()).append('\n');
+        }
+        
+        if (getReadableChanged()) {
+            buffer.append("    readable changed from ").append(!isReadable()).append(" to ").append(isReadable()).append('\n');
+        }
+
+        if (getWriteableChanged()) {
+            buffer.append("    writeable changed from ").append(!isWriteable()).append(" to ").append(isWriteable()).append('\n');
+        }
+
+        buffer.deleteCharAt(buffer.length() - 1);
+
+        return buffer.toString();
+    }
+
+}
diff --git a/src/org/jdesktop/beansbinding/PropertyStateListener.java b/src/org/jdesktop/beansbinding/PropertyStateListener.java
new file mode 100644
index 0000000..5c74370
--- /dev/null
+++ b/src/org/jdesktop/beansbinding/PropertyStateListener.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.beansbinding;
+
+import java.util.EventListener;
+
+/**
+ * {@code PropertyStateListeners} are registerd on {@link org.jdesktop.beansbinding.Property}
+ * instances, to be notified when the state of the property changes.
+ *
+ * @author Shannon Hickey
+ */
+public interface PropertyStateListener extends EventListener {
+
+    /**
+     * Called to notify the listener that a change of state has occurred to
+     * one of the {@code Property} instances upon which the listener is registered.
+     *
+     * @param pse an event describing the state change, {@code non-null}
+     */
+    public void propertyStateChanged(PropertyStateEvent pse);
+
+}
diff --git a/src/org/jdesktop/beansbinding/TempELContext.java b/src/org/jdesktop/beansbinding/TempELContext.java
new file mode 100644
index 0000000..0925f77
--- /dev/null
+++ b/src/org/jdesktop/beansbinding/TempELContext.java
@@ -0,0 +1,106 @@
+package org.jdesktop.beansbinding;
+
+import java.util.*;
+import java.beans.*;
+import org.jdesktop.beansbinding.ext.BeanAdapterFactory;
+import org.jdesktop.el.BeanELResolver;
+import org.jdesktop.el.CompositeELResolver;
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.ELResolver;
+import org.jdesktop.el.FunctionMapper;
+import org.jdesktop.el.MapELResolver;
+import org.jdesktop.el.VariableMapper;
+import org.jdesktop.el.impl.lang.FunctionMapperImpl;
+import org.jdesktop.el.impl.lang.VariableMapperImpl;
+
+/**
+ * This class is temporary. Moving forward, we'll instead have a factory for
+ * configuring this.
+ *
+ * @author Shannon Hickey
+ */
+class TempELContext extends ELContext {
+    private final CompositeELResolver resolver;
+    private final VariableMapper variableMapper = new VariableMapperImpl();
+    private final FunctionMapper functionMapper = new FunctionMapperImpl();
+    
+    public TempELContext() {
+        resolver = new CompositeELResolver();
+        // PENDING(shannonh) - EL also has an ArrayELResolver. Should that be added too?
+        resolver.add(new MapELResolver());
+        resolver.add(new BeanDelegateELResolver());
+    }
+    
+    public ELResolver getELResolver() {
+        return resolver;
+    }
+    
+    public FunctionMapper getFunctionMapper() {
+        return functionMapper;
+    }
+    
+    public VariableMapper getVariableMapper() {
+        return variableMapper;
+    }
+    
+    private class BeanDelegateELResolver extends BeanELResolver {
+        public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
+            Iterator<FeatureDescriptor> superDescriptors = super.getFeatureDescriptors(context, base);
+
+            if (base != null) {
+                List<PropertyDescriptor> pds = BeanAdapterFactory.getAdapterPropertyDescriptors(base.getClass());
+                if (pds.size() > 0) {
+                    Map<String, FeatureDescriptor> fdMap = new HashMap<String,FeatureDescriptor>();
+
+                    while (superDescriptors.hasNext()) {
+                        FeatureDescriptor fd = superDescriptors.next();
+                        fdMap.put(fd.getName(), fd);
+                    }
+
+                    for (PropertyDescriptor pd : pds) {
+                        if (pd.getPropertyType() != null) {
+                            pd.setValue("type", pd.getPropertyType());
+                            pd.setValue("resolvableAtDesignTime", Boolean.TRUE);
+                            fdMap.put(pd.getName(), pd);
+                        }
+                    }
+
+                    return fdMap.values().iterator();
+                }
+            }
+
+            return superDescriptors;
+        }
+
+        private Object baseOrAdapter(Object base, Object property) {
+            if (base != null && property instanceof String) {
+                Object adapter = BeanAdapterFactory.getAdapter(base, (String) property);
+                if (adapter != null) {
+                    return adapter;
+                }
+            }
+
+            return base;
+        }
+
+        public void setValue(ELContext context, Object base, Object property, Object val) {
+            super.setValue(context, baseOrAdapter(base, property), property, val);
+        }
+
+        public boolean isReadOnly(ELContext context, Object base, Object property) {
+            return super.isReadOnly(context, baseOrAdapter(base, property), 
+                    property);
+        }
+
+        public Object getValue(ELContext context, Object base, Object property) {
+            return super.getValue(context, baseOrAdapter(base, property), 
+                    property);
+        }
+
+        public Class<?> getType(ELContext context, Object base, Object property) {
+            return super.getType(context, baseOrAdapter(base, property), 
+                    property);
+        }
+    }
+
+}
diff --git a/src/org/jdesktop/beansbinding/Validator.java b/src/org/jdesktop/beansbinding/Validator.java
new file mode 100644
index 0000000..d36889d
--- /dev/null
+++ b/src/org/jdesktop/beansbinding/Validator.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.beansbinding;
+
+/**
+ * {@code Validator} is responsible for validating the value from the target of
+ * a {@code Binding}.
+ *
+ * @param <T> the type of value that this validator can validate
+ *
+ * @author Shannon Hickey
+ */ 
+public abstract class Validator<T> {
+
+    /**
+     * An instance of {@code Result} is returned from a {@code Validator's}
+     * {@code validate} method to indicate an invalid value.
+     * <p>
+     * A {@code Result} can contain an error code and/or description.
+     * These values are for your own reporting purposes and are not used
+     * internally.
+     */
+    public class Result {
+        private final Object errorCode;
+        private final String description;
+
+        /**
+         * Creates a {@code Result} with the given error code and description.
+         *
+         * @param errorCode an error code for this {@code Result}, may be {@code null}
+         * @param description a textual description of the {@code Result}, may be {@code null}
+         */
+        public Result(Object errorCode, String description) {
+            this.description = description;
+            this.errorCode = errorCode;
+        }
+
+        /**
+         * Returns the error code for the result, which may be {@code null}.
+         *
+         * @return the error code
+         */
+        public Object getErrorCode() {
+            return errorCode;
+        }
+
+        /**
+         * Returns a description of the validation result, which may be {@code null}.
+         *
+         * @return the description
+         */
+        public String getDescription() {
+            return description;
+        }
+        
+        /**
+         * Returns a string representation of the {@code Result}. This
+         * method is intended to be used for debugging purposes only, and
+         * the content and format of the returned string may vary between
+         * implementations. The returned string may be empty but may not
+         * be {@code null}.
+         *
+         * @return a string representation of this {@code Result}
+         */
+        public String toString() {
+            return getClass().getName() +
+                    " [" +
+                    "errorCode=" + errorCode +
+                    ", description=" + description +
+                    "]";
+        }
+    }
+
+    /**
+     * Validates a value; returns {@code null} for a valid value, and a
+     * {@code Result} object describing the problem for an invalid value.
+     *
+     * @param value the value to validate, may be {@code null}
+     * @return {@code null} for a valid value or a {@code Result}
+     *         describing the problem for an invalid value
+     */
+    public abstract Result validate(T value);
+}
diff --git a/src/org/jdesktop/beansbinding/ext/BeanAdapterFactory.java b/src/org/jdesktop/beansbinding/ext/BeanAdapterFactory.java
new file mode 100644
index 0000000..bd16137
--- /dev/null
+++ b/src/org/jdesktop/beansbinding/ext/BeanAdapterFactory.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.beansbinding.ext;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.lang.ref.WeakReference;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.beans.*;
+
+public final class BeanAdapterFactory {
+    private static final BeanAdapterFactory INSTANCE =  new BeanAdapterFactory();
+    private final Map<Object, List<VendedAdapter>> vendedAdapters;
+    private final List<BeanAdapterProvider> providers;
+    private final Set<ClassLoader> classLoaders;
+    private final Set<URL> serviceURLs;
+
+    public static Object getAdapter(Object source, String property) {
+        return INSTANCE.getAdapter0(source, property);
+    }
+
+    public static List<PropertyDescriptor> getAdapterPropertyDescriptors(Class<?> type) {
+        return INSTANCE.getAdapterPropertyDescriptors0(type);
+    }
+
+    public BeanAdapterFactory() {
+        this.providers = new ArrayList<BeanAdapterProvider>();
+        classLoaders = new HashSet<ClassLoader>();
+        serviceURLs = new HashSet<URL>();
+        vendedAdapters = new WeakHashMap<Object, List<VendedAdapter>>();
+    }
+
+    private void loadProvidersIfNecessary() {
+        ClassLoader currentLoader = Thread.currentThread().getContextClassLoader();
+        if (!classLoaders.contains(currentLoader)) {
+            classLoaders.add(currentLoader);
+            loadProviders(currentLoader);
+        }
+    }
+    
+    private void loadProviders(ClassLoader classLoader) {
+        // PENDING: this needs to be rewriten in terms of ServiceLoader
+        String serviceName = "META-INF/services/" + 
+                BeanAdapterProvider.class.getName();
+        try {
+            Enumeration<URL> urls = classLoader.getResources(serviceName);
+            while (urls.hasMoreElements()) {
+                URL url = urls.nextElement();
+                if (!serviceURLs.contains(url)) {
+                    serviceURLs.add(url);
+                    addProviders(url);
+                }
+            }
+        } catch (IOException ex) {
+        }
+    }
+    
+    private void addProviders(URL url) {
+        InputStream inputStream = null;
+        BufferedReader reader = null;
+        try {
+            inputStream = url.openStream();
+            reader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
+            String line;
+            while ((line = reader.readLine()) != null) {
+                try {
+                    providers.add((BeanAdapterProvider)Class.forName(line).newInstance());
+                } catch (IllegalAccessException ex) {
+                } catch (InstantiationException ex) {
+                } catch (ClassNotFoundException ex) {
+                }
+            }
+        } catch (UnsupportedEncodingException ex) {
+        } catch (IOException ex) {
+        }
+        if (reader != null) {
+            try {
+                reader.close();
+            } catch (IOException ex) {
+            }
+        }
+    }
+
+    public Object getAdapter0(Object source, String property) {
+        if (source == null || property == null) {
+            throw new IllegalArgumentException();
+        }
+        loadProvidersIfNecessary();
+        property = property.intern();
+        BeanAdapterProvider provider = getProvider(source, property);
+        if (provider != null) {
+            List<VendedAdapter> adapters = vendedAdapters.get(source);
+            if (adapters != null) {
+                for (int i = adapters.size() - 1; i >= 0; i--) {
+                    VendedAdapter vendedAdapter = adapters.get(i);
+                    Object adapter = vendedAdapter.getAdapter();
+                    if (adapter == null) {
+                        vendedAdapters.remove(i);
+                    } else if (vendedAdapter.getProvider() == provider && vendedAdapter.getProperty() == property) {
+                        return adapter;
+                    }
+                }
+            } else {
+                adapters = new ArrayList<VendedAdapter>(1);
+                vendedAdapters.put(source, adapters);
+            }
+            Object adapter = provider.createAdapter(source, property);
+            adapters.add(new VendedAdapter(property, provider, adapter));
+            return adapter;
+        }
+        return null;
+    }
+    
+    private BeanAdapterProvider getProvider(Object source, String property) {
+        Class<?> type = source.getClass();
+        for (BeanAdapterProvider provider : providers) {
+            if (provider.providesAdapter(type, property)) {
+                return provider;
+            }
+        }
+        return null;
+    }
+
+        private List<FeatureDescriptor> getDescriptors(Class<?> type) {
+            BeanInfo info = null;
+            try {
+                info = Introspector.getBeanInfo(type);
+            } catch (Exception ex) {
+            }
+            if (info == null) {
+                return Collections.emptyList();
+            }
+            ArrayList<FeatureDescriptor> list = new ArrayList<FeatureDescriptor>(
+                    info.getPropertyDescriptors().length);
+            for (PropertyDescriptor pd: info.getPropertyDescriptors()) {
+                // PENDING: The following properties come from EL, are they
+                // needed?
+                if (pd.getPropertyType() != null) {
+                    pd.setValue("type", pd.getPropertyType());
+                }
+                pd.setValue("resolvableAtDesignTime", Boolean.TRUE);
+                list.add(pd);
+            }
+            return list;
+        }
+
+    private static BeanInfo getBeanInfo(Class<?> type) {
+        try {
+            return Introspector.getBeanInfo(type);
+        } catch (IntrospectionException ie) {
+            return null;
+        }
+    }
+
+    private List<PropertyDescriptor> getAdapterPropertyDescriptors0(Class<?> type) {
+        if (type == null) {
+            throw new IllegalArgumentException("Type must be non-null");
+        }
+
+        loadProvidersIfNecessary();
+        
+        ArrayList<PropertyDescriptor> des = new ArrayList<PropertyDescriptor>();
+
+        for (BeanAdapterProvider provider : providers) {
+            Class<?> pdType = provider.getAdapterClass(type);
+            if (pdType != null) {
+                BeanInfo info = getBeanInfo(pdType);
+                if (info != null) {
+                    PropertyDescriptor[] pds = info.getPropertyDescriptors();
+                    if (pds != null) {
+                        for (PropertyDescriptor pd : pds) {
+                            if (provider.providesAdapter(type, pd.getName())) {
+                                des.add(pd);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        
+        return des;
+    }
+    
+    private static final class VendedAdapter {
+        private final BeanAdapterProvider provider;
+        private final String property;
+        private final WeakReference<Object> adapter;
+
+        public VendedAdapter(String property, BeanAdapterProvider provider, Object adapter) {
+            this.property = property;
+            this.adapter = new WeakReference<Object>(adapter);
+            this.provider = provider;
+        }
+
+        public Object getAdapter() {
+            return adapter.get();
+        }
+
+        public String getProperty() {
+            return property;
+        }
+        
+        public BeanAdapterProvider getProvider() {
+            return provider;
+        }
+    }
+
+}
diff --git a/src/org/jdesktop/beansbinding/ext/BeanAdapterProvider.java b/src/org/jdesktop/beansbinding/ext/BeanAdapterProvider.java
new file mode 100644
index 0000000..41c6f7b
--- /dev/null
+++ b/src/org/jdesktop/beansbinding/ext/BeanAdapterProvider.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.beansbinding.ext;
+
+/**
+ * @author sky
+ * @author Shannon Hickey
+ */
+public interface BeanAdapterProvider {
+
+    public abstract boolean providesAdapter(Class<?> type, String property);
+    public abstract Object createAdapter(Object source, String property);
+    public abstract Class<?> getAdapterClass(Class<?> type);
+
+}
diff --git a/src/org/jdesktop/beansbinding/ext/package.html b/src/org/jdesktop/beansbinding/ext/package.html
new file mode 100644
index 0000000..cadaeba
--- /dev/null
+++ b/src/org/jdesktop/beansbinding/ext/package.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<!--
+ Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ subject to license terms.
+-->
+
+<html>
+    <head>
+        <title></title>
+    </head>
+    <body>
+        <p>
+            Provides support for customizing Beans Binding, and for
+            determining the synthetic properties available for binding.
+            Tool vendors and component authors are the expected audience
+            of this package.
+        </p>
+        <p>
+            Some {@code Property} implementations, such as {@code BeanProperty}
+            and {@code ELProperty}, resolve properties of an object at runtime
+            as per the Java Beans specification. Sometimes, properties of interest aren't
+            exposed according to this specification (for example, Swing's
+            {@code JTextField.text}, {@code JSlider.value}, {@code AbstractButton.selected}).
+            At other times, we may want to extend an object's set of properties
+            with synthetic properties that may be interesting for the purposes of
+            binding (for example {@code selectedElement(s)}) on Swing's {@code JTable}
+            and {@code JList}.
+        </p>
+        <p>
+            This package provides for these scenarios by allowing you to provide
+            adapters to adapt these objects and expose such properties as proper
+            Java Beans properties. This is done by writing one or more
+            {@link org.jdesktop.beansbinding.ext.BeanAdapterProvider}
+            classes and registering them via the service provider mechanism
+            <font color="red">(to be implemented)</font> with the {@link
+            org.jdesktop.beansbinding.ext.BeanAdapterFactory}.
+        </p>
+        <p>
+            {@code Property} implementations can then query the {@code BeanAdapterFactory}
+            for adapters to take the place of the source object for the purposes
+            of property resolution. This is exactly what {@code BeanProperty} and
+            {@code ELProperty} do. In addition, tools can query the factory for
+            the set of additional properties exposed for a particular class by
+            the set of loaded providers.
+        </p>
+        <p>
+            Note: Adapters for many Swing properties are already registered with the
+            factory. See the <a href="../../swingbinding/package-summary.html">swingbinding</a>
+            package level documentation for details.
+        </p>
+    </body>
+</html>
diff --git a/src/org/jdesktop/beansbinding/package.html b/src/org/jdesktop/beansbinding/package.html
new file mode 100644
index 0000000..77e7c3e
--- /dev/null
+++ b/src/org/jdesktop/beansbinding/package.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<!--
+ Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ subject to license terms.
+-->
+
+<html>
+    <head>
+        <title></title>
+    </head>
+    <body>
+        <p>
+            Provides support for defining properties and creating bindings between
+            sets of two properties.
+        </p>
+        <h3>Properties</h3>
+        <p>
+            A property is defined by creating an instance of a concrete implementation
+            of the {@link org.jdesktop.beansbinding.Property} class. This package
+            provides two concrete {@code Property} implementations of interest:
+            {@link org.jdesktop.beansbinding.BeanProperty} allows Java Bean properties
+            to be addressed by providing their path as a String.
+            {@link org.jdesktop.beansbinding.ELProperty} allows Java Bean properties
+            to be addressed similarly and then be used in various ways in an EL expression.
+            A simple {@code BeanProperty} that refers to a "firstName" Java Bean property,
+            might look like:
+        </p>
+        <pre><code>
+    BeanProperty firstNameP = BeanProperty.create("firstName");
+        </code></pre>
+        <p>
+            An {@code ELProperty} to combine the "firstName" and "lastName"
+            properties of a Java Bean into a full name, might look like:
+        </p>
+        <pre><code>
+    ELProperty fullNameP = ELProperty.create("${firstName} ${lastName}");
+        </code></pre>
+        <p>
+            These property objects can then be used to operate on a Java Bean having
+            the properties of interest. For example:
+        </p>
+        <pre><code>
+    // prints the first name on the person object
+    System.out.println(firstNameP.getValue(person));
+
+    // prints the full name of the person object
+    System.out.println(fullNameP.getValue(person));
+
+    // sets the first name of the person object to "Duke"
+    firstNameP.setValue(person, "Duke");
+
+    // listen for changes to the person's full name
+    fullNameP.addPropertyStateListener(person, listener);
+        </code></pre>
+        <p>
+            Both of these property implementations are designed to work with bean
+            properties that follow the Java Beans specification. Sometimes, however,
+            a property of interest on a bean outside of your control may not be exposed
+            in the correct way, and you want to adapt it for use in binding.
+            Other times you may want to extend the set of properties that a bean
+            exposes, simply for use in binding. These cases are provided for by
+            the <a href="ext/package-summary.html">org.jdesktop.beansbinding.ext</a>
+            package.
+        </p>
+        <h3>Bindings</h3>
+        <p>
+            A binding is created between two {@code Property} instances, and the objects
+            on which the {@code Property} objects should operate, by creating an instance
+            of a concrete implementation of the {@code Binding} class. Once the binding is
+            realized, by a call to the {@code bind} method, a {@code Binding} starts
+            tracking changes to the properties on both ends, and a typical {@code Binding}
+            implementation will sync the properties with each other based on on some defined
+            strategy.
+        </p>
+            This package provides one concrete subclass of {@code Binding} called
+            {@link org.jdesktop.beansbinding.AutoBinding} which syncs the properties based
+            on a configurable update strategy. {@code AutoBindings} are created by
+            calling one of the static {@code createAutoBinding} methods in the
+            {@link org.jdesktop.beansbinding.Bindings} class. For example:
+        </p>
+        <pre><code>
+    BeanProperty firstNameP = BeanProperty.create("firstName");
+    BeanProperty textP = BeanProperty.create("text");
+    Binding binding = Bindings.createAutoBinding(READ_WRITE, person, firstNameP, jTextField, textP);
+    binding.bind();
+        </code></pre>
+        <p>
+            Before a value from a source property is set on a target property, it passes
+            through an optional {@link org.jdesktop.beansbinding.Converter} to convert
+            it between the source type and target type. Before a value passed from a
+            target property back to a source property, it passes first through the
+            optional {@code Converter} and then through an optional
+            {@link org.jdesktop.beansbinding.Validator}, which can reject invalid values.
+        </p>
+    </body>
+</html>
diff --git a/src/org/jdesktop/el/ArrayELResolver.java b/src/org/jdesktop/el/ArrayELResolver.java
new file mode 100644
index 0000000..589ccac
--- /dev/null
+++ b/src/org/jdesktop/el/ArrayELResolver.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el;
+
+import java.lang.reflect.Array;
+import java.util.List;
+import java.util.Iterator;
+import java.beans.FeatureDescriptor;
+
+/**
+ * Defines property resolution behavior on arrays.
+ *
+ * <p>This resolver handles base objects that are Java language arrays.
+ * It accepts any object as a property and coerces that object into an
+ * integer index into the array. The resulting value is the value in the array
+ * at that index.</p>
+ *
+ * <p>This resolver can be constructed in read-only mode, which means that
+ * {@link #isReadOnly} will always return <code>true</code> and 
+ * {@link #setValue} will always throw
+ * <code>PropertyNotWritableException</code>.</p>
+ *
+ * <p><code>ELResolver</code>s are combined together using 
+ * {@link CompositeELResolver}s, to define rich semantics for evaluating 
+ * an expression. See the javadocs for {@link ELResolver} for details.</p>
+ *
+ * @see CompositeELResolver
+ * @see ELResolver
+ * @since JSP 2.1
+ */
+public class ArrayELResolver extends ELResolver {
+
+    /**
+     * Creates a new read/write <code>ArrayELResolver</code>.
+     */
+    public ArrayELResolver() {
+        this.isReadOnly = false;
+    }
+
+    /**
+     * Creates a new <code>ArrayELResolver</code> whose read-only status is
+     * determined by the given parameter.
+     *
+     * @param isReadOnly <code>true</code> if this resolver cannot modify
+     *     arrays; <code>false</code> otherwise.
+     */
+    public ArrayELResolver(boolean isReadOnly) {
+        this.isReadOnly = isReadOnly;
+    }
+
+    /**
+     * If the base object is an array, returns the most general acceptable type 
+     * for a value in this array.
+     *
+     * <p>If the base is a <code>array</code>, the
+     * <code>propertyResolved</code> property of the <code>ELContext</code>
+     * object must be set to <code>true</code> by this resolver, before
+     * returning. If this property is not <code>true</code> after this method
+     * is called, the caller should ignore the return value.</p>
+     *
+     * <p>Assuming the base is an <code>array</code>, this method will always
+     * return <code>base.getClass().getComponentType()</code>, which is
+     * the most general type of component that can be stored at any given
+     * index in the array.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The array to analyze. Only bases that are Java language
+     *     arrays are handled by this resolver.
+     * @param property The index of the element in the array to return the 
+     *     acceptable type for. Will be coerced into an integer, but 
+     *     otherwise ignored by this resolver.
+     * @return If the <code>propertyResolved</code> property of 
+     *     <code>ELContext</code> was set to <code>true</code>, then
+     *     the most general acceptable type; otherwise undefined.
+     * @throws PropertyNotFoundException if the given index is out of 
+     *     bounds for this array.
+     * @throws NullPointerException if context is <code>null</code>
+     * @throws ELException if an exception was thrown while performing
+     *     the property or variable resolution. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     */
+    public Class<?> getType(ELContext context,
+                         Object base,
+                         Object property) {
+
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base != null && base.getClass().isArray()) {
+            context.setPropertyResolved(true);
+            int index = toInteger (property);
+            if (index < 0 || index >= Array.getLength(base)) {
+                throw new PropertyNotFoundException();
+            }
+            return base.getClass().getComponentType();
+        }
+        return null;
+    }
+
+    /**
+     * If the base object is a Java language array, returns the value at the 
+     * given index. The index is specified by the <code>property</code> 
+     * argument, and coerced into an integer. If the coercion could not be 
+     * performed, an <code>IllegalArgumentException</code> is thrown. If the
+     * index is out of bounds, <code>null</code> is returned.
+     *
+     * <p>If the base is a Java language array, the
+     * <code>propertyResolved</code> property of the <code>ELContext</code>
+     * object must be set to <code>true</code> by this resolver, before 
+     * returning. If this property is not <code>true</code> after this 
+     * method is called, the caller should ignore the return value.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The array to analyze. Only bases that are Java language
+     *     arrays are handled by this resolver.
+     * @param property The index of the value to be returned. Will be coerced
+     *     into an integer.
+     * @return If the <code>propertyResolved</code> property of 
+     *     <code>ELContext</code> was set to <code>true</code>, then
+     *     the value at the given index or <code>null</code>
+     *     if the index was out of bounds. Otherwise, undefined.
+     * @throws IllegalArgumentException if the property could not be coerced
+     *     into an integer.
+     * @throws NullPointerException if context is <code>null</code>.
+     * @throws ELException if an exception was thrown while performing
+     *     the property or variable resolution. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     */
+    public Object getValue(ELContext context,
+                           Object base,
+                           Object property) {
+
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base != null && base.getClass().isArray()) {
+            context.setPropertyResolved(true);
+            int index = toInteger (property);
+            if (index >= 0 && index < Array.getLength(base)) {
+                return Array.get(base, index);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * If the base object is a Java language array, attempts to set the 
+     * value at the given index with the given value. The index is specified 
+     * by the <code>property</code> argument, and coerced into an integer. 
+     * If the coercion could not be performed, an 
+     * <code>IllegalArgumentException</code> is thrown. If the index is
+     * out of bounds, a <code>PropertyNotFoundException</code> is thrown.
+     *
+     * <p>If the base is a Java language array, the
+     * <code>propertyResolved</code> property of the <code>ELContext</code>
+     * object must be set to <code>true</code> by this resolver, before
+     * returning. If this property is not <code>true</code> after this method
+     * is called, the caller can safely assume no value was set.</p>
+     *
+     * <p>If this resolver was constructed in read-only mode, this method will
+     * always throw <code>PropertyNotWritableException</code>.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The array to be modified. Only bases that are Java language
+     *     arrays are handled by this resolver.
+     * @param property The index of the value to be set. Will be coerced
+     *     into an integer.
+     * @param val The value to be set at the given index.
+     * @throws ClassCastException if the class of the specified element 
+     *     prevents it from being added to this array.
+     * @throws NullPointerException if context is <code>null</code>.
+     * @throws IllegalArgumentException if the property could not be coerced
+     *     into an integer, or if some aspect of the specified element 
+     *     prevents it from being added to this array.
+     * @throws PropertyNotWritableException if this resolver was constructed
+     *     in read-only mode.
+     * @throws ELException if an exception was thrown while performing
+     *     the property or variable resolution. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     */
+    public void setValue(ELContext context,
+                         Object base,
+                         Object property,
+                         Object val) {
+
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base != null && base.getClass().isArray()) {
+            context.setPropertyResolved(true);
+            if (isReadOnly) {
+                throw new PropertyNotWritableException();
+            }
+            Class<?> type = base.getClass().getComponentType();
+            if (val != null && ! type.isAssignableFrom(val.getClass())) {
+                throw new ClassCastException();
+            }
+            int index = toInteger (property);
+            if (index < 0 || index >= Array.getLength(base)) {
+                throw new PropertyNotFoundException();
+            }
+            Array.set(base, index, val);
+        }
+    }
+
+    /**
+     * If the base object is a Java language array, returns whether a call to 
+     * {@link #setValue} will always fail.
+     *
+     * <p>If the base is a Java language array, the
+     * <code>propertyResolved</code> property of the <code>ELContext</code>
+     * object must be set to <code>true</code> by this resolver, before
+     * returning. If this property is not <code>true</code> after this method
+     * is called, the caller should ignore the return value.</p>
+     *
+     * <p>If this resolver was constructed in read-only mode, this method will
+     * always return <code>true</code>. Otherwise, it returns 
+     * <code>false</code>.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The array to analyze. Only bases that are a Java language
+     *     array are handled by this resolver.
+     * @param property The index of the element in the array to return the 
+     *     acceptable type for. Will be coerced into an integer, but 
+     *     otherwise ignored by this resolver.
+     * @return If the <code>propertyResolved</code> property of 
+     *     <code>ELContext</code> was set to <code>true</code>, then
+     *     <code>true</code> if calling the <code>setValue</code> method
+     *     will always fail or <code>false</code> if it is possible that
+     *     such a call may succeed; otherwise undefined.
+     * @throws PropertyNotFoundException if the given index is out of 
+     *     bounds for this array.
+     * @throws NullPointerException if context is <code>null</code>
+     * @throws ELException if an exception was thrown while performing
+     *     the property or variable resolution. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     */
+    public boolean isReadOnly(ELContext context,
+                              Object base,
+                              Object property) {
+
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base != null && base.getClass().isArray()) {
+            context.setPropertyResolved(true);
+            int index = toInteger (property);
+            if (index < 0 || index >= Array.getLength(base)) {
+                throw new PropertyNotFoundException();
+            }
+        }
+        return isReadOnly;
+    }
+
+    /**
+     * Always returns <code>null</code>, since there is no reason to 
+     * iterate through set set of all integers.
+     *
+     * <p>The {@link #getCommonPropertyType} method returns sufficient
+     * information about what properties this resolver accepts.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The array to analyze. Only bases that are a Java language
+     *     array are handled by this resolver.
+     * @return <code>null</code>.
+     */
+    public Iterator<FeatureDescriptor> getFeatureDescriptors(
+                                          ELContext context,
+                                          Object base) {
+        return null;
+    }
+
+    /**
+     * If the base object is a Java language array, returns the most general 
+     * type that this resolver accepts for the <code>property</code> argument.
+     * Otherwise, returns <code>null</code>.
+     *
+     * <p>Assuming the base is an array, this method will always return 
+     * <code>Integer.class</code>. This is because arrays accept integers
+     * for their index.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The array to analyze. Only bases that are a Java language
+     *     array are handled by this resolver.
+     * @return <code>null</code> if base is not a Java language array;
+     *     otherwise <code>Integer.class</code>.
+     */
+    public Class<?> getCommonPropertyType(ELContext context,
+                                               Object base) {
+
+        if (base != null && base.getClass().isArray()) {
+            return Integer.class;
+        }
+        return null;
+    }
+
+    private int toInteger(Object p) {
+
+        if (p instanceof Integer) {
+            return ((Integer) p).intValue();
+        }
+        if (p instanceof Character) {
+            return ((Character) p).charValue();
+        }
+        if (p instanceof Boolean) {
+            return ((Boolean) p).booleanValue()? 1: 0;
+        }
+        if (p instanceof Number) {
+            return ((Number) p).intValue();
+        }
+        if (p instanceof String) {
+            return Integer.parseInt((String) p);
+        }
+        throw new IllegalArgumentException();
+    }
+
+    private boolean isReadOnly;
+}
+
diff --git a/src/org/jdesktop/el/BeanELResolver.java b/src/org/jdesktop/el/BeanELResolver.java
new file mode 100644
index 0000000..44dca58
--- /dev/null
+++ b/src/org/jdesktop/el/BeanELResolver.java
@@ -0,0 +1,542 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.beans.FeatureDescriptor;
+import java.beans.BeanInfo;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.beans.IntrospectionException;
+import java.util.Iterator;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Defines property resolution behavior on objects using the JavaBeans
+ * component architecture.
+ *
+ * <p>This resolver handles base objects of any type, as long as the
+ * base is not <code>null</code>. It accepts any object as a property, and
+ * coerces it to a string. That string is then used to find a JavaBeans
+ * compliant property on the base object. The value is accessed using
+ * JavaBeans getters and setters.</p>
+ * 
+ * <p>This resolver can be constructed in read-only mode, which means that
+ * {@link #isReadOnly} will always return <code>true</code> and 
+ * {@link #setValue} will always throw
+ * <code>PropertyNotWritableException</code>.</p>
+ *
+ * <p><code>ELResolver</code>s are combined together using 
+ * {@link CompositeELResolver}s, to define rich semantics for evaluating 
+ * an expression. See the javadocs for {@link ELResolver} for details.</p>
+ *
+ * <p>Because this resolver handles base objects of any type, it should
+ * be placed near the end of a composite resolver. Otherwise, it will
+ * claim to have resolved a property before any resolvers that come after
+ * it get a chance to test if they can do so as well.</p>
+ *
+ * @see CompositeELResolver
+ * @see ELResolver
+ * @since JSP 2.1
+ */
+public class BeanELResolver extends ELResolver {
+
+    private boolean isReadOnly;
+
+    // Set a limit for the number of beans in the cache.
+    private final static int SIZE = 2000;
+    // Two maps are used here, to facilitate discarding the older cache
+    // when cache size reaches the limit.
+    private static final Map<Class, BeanProperties> properties =
+        new ConcurrentHashMap<Class, BeanProperties>(SIZE);
+    private static final Map<Class, BeanProperties> properties2 =
+        new ConcurrentHashMap<Class, BeanProperties>(SIZE);
+                                                                                
+    /*
+     * Defines a property for a bean.
+     */
+    protected final static class BeanProperty {
+
+        private Method readMethod;
+        private Method writeMethod;
+        private Class baseClass;
+        private PropertyDescriptor descriptor;
+                                                                                
+        public BeanProperty(Class<?> baseClass,
+                            PropertyDescriptor descriptor) {
+            this.baseClass = baseClass;
+            this.descriptor = descriptor;
+        }
+                                                                                
+        public Class getPropertyType() {
+            return descriptor.getPropertyType();
+        }
+                                                                                
+        public boolean isReadOnly() {
+            return getWriteMethod() == null;
+        }
+                                                                                
+        public Method getReadMethod() {
+            if (readMethod == null) {
+                readMethod = getMethod(baseClass, descriptor.getReadMethod());
+            }
+            return readMethod;
+        }
+                                                                                
+        public Method getWriteMethod() {
+            if (writeMethod == null) {
+                writeMethod = getMethod(baseClass, descriptor.getWriteMethod());
+            }
+            return writeMethod;
+        }
+    }
+                                                                                
+    /*
+     * Defines the properties for a bean.
+     */
+    protected final static class BeanProperties {
+
+        private final Class baseClass;
+        private final Map<String, BeanProperty> propertyMap =
+            new HashMap<String, BeanProperty>();
+                                                                                
+        public BeanProperties(Class<?> baseClass) {
+            this.baseClass = baseClass;
+            PropertyDescriptor[] descriptors;
+            try {
+                BeanInfo info = Introspector.getBeanInfo(baseClass);
+                descriptors = info.getPropertyDescriptors();
+            } catch (IntrospectionException ie) {
+                throw new ELException(ie);
+            }
+            for (PropertyDescriptor pd: descriptors) {
+                propertyMap.put(pd.getName(),
+                                new BeanProperty(baseClass, pd));
+            }
+        }
+                                                                                
+        public BeanProperty getBeanProperty(String property) {
+            return propertyMap.get(property);
+        }
+    }
+
+    /**
+     * Creates a new read/write <code>BeanELResolver</code>.
+     */
+    public BeanELResolver() {
+        this.isReadOnly = false;
+    }
+
+    /**
+     * Creates a new <code>BeanELResolver</code> whose read-only status is
+     * determined by the given parameter.
+     *
+     * @param isReadOnly <code>true</code> if this resolver cannot modify
+     *     beans; <code>false</code> otherwise.
+     */
+    public BeanELResolver(boolean isReadOnly) {
+        this.isReadOnly = isReadOnly;
+    }
+
+    /**
+     * If the base object is not <code>null</code>, returns the most 
+     * general acceptable type that can be set on this bean property.
+     *
+     * <p>If the base is not <code>null</code>, the 
+     * <code>propertyResolved</code> property of the <code>ELContext</code>
+     * object must be set to <code>true</code> by this resolver, before
+     * returning. If this property is not <code>true</code> after this 
+     * method is called, the caller should ignore the return value.</p>
+     *
+     * <p>The provided property will first be coerced to a <code>String</code>.
+     * If there is a <code>BeanInfoProperty</code> for this property and
+     * there were no errors retrieving it, the <code>propertyType</code> of
+     * the <code>propertyDescriptor</code> is returned. Otherwise, a
+     * <code>PropertyNotFoundException</code> is thrown.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The bean to analyze.
+     * @param property The name of the property to analyze. Will be coerced to
+     *     a <code>String</code>.
+     * @return If the <code>propertyResolved</code> property of 
+     *     <code>ELContext</code> was set to <code>true</code>, then
+     *     the most general acceptable type; otherwise undefined.
+     * @throws NullPointerException if context is <code>null</code>
+     * @throws PropertyNotFoundException if <code>base</code> is not
+     *     <code>null</code> and the specified property does not exist
+     *     or is not readable.
+     * @throws ELException if an exception was thrown while performing
+     *     the property or variable resolution. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     */
+    public Class<?> getType(ELContext context,
+                         Object base,
+                         Object property) {
+
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base == null || property == null){
+            return null;
+        }
+
+        BeanProperty bp = getBeanProperty(context, base, property);
+        context.setPropertyResolved(true);
+        return bp.getPropertyType();
+    }
+
+    /**
+     * If the base object is not <code>null</code>, returns the current
+     * value of the given property on this bean.
+     *
+     * <p>If the base is not <code>null</code>, the 
+     * <code>propertyResolved</code> property of the <code>ELContext</code>
+     * object must be set to <code>true</code> by this resolver, before
+     * returning. If this property is not <code>true</code> after this 
+     * method is called, the caller should ignore the return value.</p>
+     *
+     * <p>The provided property name will first be coerced to a
+     * <code>String</code>. If the property is a readable property of the 
+     * base object, as per the JavaBeans specification, then return the 
+     * result of the getter call. If the getter throws an exception, 
+     * it is propagated to the caller. If the property is not found or is 
+     * not readable, a <code>PropertyNotFoundException</code> is thrown.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The bean on which to get the property.
+     * @param property The name of the property to get. Will be coerced to
+     *     a <code>String</code>.
+     * @return If the <code>propertyResolved</code> property of 
+     *     <code>ELContext</code> was set to <code>true</code>, then
+     *     the value of the given property. Otherwise, undefined.
+     * @throws NullPointerException if context is <code>null</code>.
+     * @throws PropertyNotFoundException if <code>base</code> is not
+     *     <code>null</code> and the specified property does not exist
+     *     or is not readable.
+     * @throws ELException if an exception was thrown while performing
+     *     the property or variable resolution. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     */
+    public Object getValue(ELContext context,
+                           Object base,
+                           Object property) {
+
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base == null || property == null) {
+            return null;
+        }
+
+        Method method;
+        BeanProperty bp = getBeanProperty(context, base, property);
+        if (bp == null || (method = bp.getReadMethod()) == null) {
+            return null;
+        }
+
+        Object value;
+        try {
+            value = method.invoke(base, new Object[0]);
+            context.setPropertyResolved(true);
+        } catch (ELException ex) {
+            throw ex;
+        } catch (InvocationTargetException ite) {
+            throw new ELException(ite.getCause());
+        } catch (Exception ex) {
+            throw new ELException(ex);
+        }
+        return value;
+    }
+
+    /**
+     * If the base object is not <code>null</code>, attempts to set the
+     * value of the given property on this bean.
+     *
+     * <p>If the base is not <code>null</code>, the 
+     * <code>propertyResolved</code> property of the <code>ELContext</code>
+     * object must be set to <code>true</code> by this resolver, before
+     * returning. If this property is not <code>true</code> after this 
+     * method is called, the caller can safely assume no value was set.</p>
+     *
+     * <p>If this resolver was constructed in read-only mode, this method will
+     * always throw <code>PropertyNotWritableException</code>.</p>
+     *
+     * <p>The provided property name will first be coerced to a
+     * <code>String</code>. If property is a writable property of 
+     * <code>base</code> (as per the JavaBeans Specification), the setter 
+     * method is called (passing <code>value</code>). If the property exists
+     * but does not have a setter, then a
+     * <code>PropertyNotFoundException</code> is thrown. If the property
+     * does not exist, a <code>PropertyNotFoundException</code> is thrown.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The bean on which to set the property.
+     * @param property The name of the property to set. Will be coerced to
+     *     a <code>String</code>.
+     * @param val The value to be associated with the specified key.
+     * @throws NullPointerException if context is <code>null</code>.
+     * @throws PropertyNotFoundException if <code>base</code> is not
+     *     <code>null</code> and the specified property does not exist.
+     * @throws PropertyNotWritableException if this resolver was constructed
+     *     in read-only mode, or if there is no setter for the property.
+     * @throws ELException if an exception was thrown while performing
+     *     the property or variable resolution. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     */
+    public void setValue(ELContext context,
+                         Object base,
+                         Object property,
+                         Object val) {
+
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base == null || property == null){
+            return;
+        }
+
+        if (isReadOnly) {
+            throw new PropertyNotWritableException(
+                        ELUtil.getExceptionMessageString(context,
+                            "resolverNotwritable",
+                            new Object[] { base.getClass().getName() }));
+        } 
+
+        BeanProperty bp = getBeanProperty(context, base, property);
+        Method method = bp.getWriteMethod();
+        if (method == null) {
+            throw new PropertyNotWritableException(
+                        ELUtil.getExceptionMessageString(context,
+                            "propertyNotWritable",
+                            new Object[] { base.getClass().getName(),
+                                           property.toString()}));
+        }
+
+        try {
+            method.invoke(base, new Object[] {val});
+            context.setPropertyResolved(true);
+        } catch (ELException ex) {
+            throw ex;
+        } catch (InvocationTargetException ite) {
+            throw new ELException(ite.getCause());
+        } catch (Exception ex) {
+            if (null == val) {
+                val = "null";
+            }
+            String message = ELUtil.getExceptionMessageString(context,
+                    "setPropertyFailed",
+                    new Object[] { property.toString(),
+                                   base.getClass().getName(), val });
+            throw new ELException(message, ex);
+        }
+    }
+
+    /**
+     * If the base object is not <code>null</code>, returns whether a call
+     * to {@link #setValue} will always fail.
+     *
+     * <p>If the base is not <code>null</code>, the 
+     * <code>propertyResolved</code> property of the <code>ELContext</code>
+     * object must be set to <code>true</code> by this resolver, before
+     * returning. If this property is not <code>true</code> after this 
+     * method is called, the caller can safely assume no value was set.</p>
+     *
+     * <p>If this resolver was constructed in read-only mode, this method will
+     * always return <code>true</code>.</p>
+     *
+     * <p>The provided property name will first be coerced to a
+     * <code>String</code>. If property is a writable property of 
+     * <code>base</code>, <code>false</code> is returned. If the property is
+     * found but is not writable, <code>true</code> is returned. If the
+     * property is not found, a <code>PropertyNotFoundException</code>
+     * is thrown.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The bean to analyze.
+     * @param property The name of the property to analyzed. Will be coerced to
+     *     a <code>String</code>.
+     * @return If the <code>propertyResolved</code> property of 
+     *     <code>ELContext</code> was set to <code>true</code>, then
+     *     <code>true</code> if calling the <code>setValue</code> method
+     *     will always fail or <code>false</code> if it is possible that
+     *     such a call may succeed; otherwise undefined.
+     * @throws NullPointerException if context is <code>null</code>
+     * @throws PropertyNotFoundException if <code>base</code> is not
+     *     <code>null</code> and the specified property does not exist.
+     * @throws ELException if an exception was thrown while performing
+     *     the property or variable resolution. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     */
+    public boolean isReadOnly(ELContext context,
+                              Object base,
+                              Object property) {
+
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base == null || property == null){
+            return false;
+        }
+
+        context.setPropertyResolved(true);
+        if (isReadOnly) {
+            return true;
+        }
+
+        BeanProperty bp = getBeanProperty(context, base, property);
+        return bp.isReadOnly();
+    }
+
+    /**
+     * If the base object is not <code>null</code>, returns an
+     * <code>Iterator</code> containing the set of JavaBeans properties
+     * available on the given object. Otherwise, returns <code>null</code>.
+     *
+     * <p>The <code>Iterator</code> returned must contain zero or more 
+     * instances of {@link java.beans.FeatureDescriptor}. Each info object 
+     * contains information about a property in the bean, as obtained by
+     * calling the <code>BeanInfo.getPropertyDescriptors</code> method.
+     * The <code>FeatureDescriptor</code> is initialized using the same
+     * fields as are present in the <code>PropertyDescriptor</code>,
+     * with the additional required named attributes "<code>type</code>" and 
+     * "<code>resolvableAtDesignTime</code>" set as follows:
+     * <dl>
+     *     <li>{@link ELResolver#TYPE} - The runtime type of the property, from
+     *         <code>PropertyDescriptor.getPropertyType()</code>.</li>
+     *     <li>{@link ELResolver#RESOLVABLE_AT_DESIGN_TIME} - <code>true</code>.</li>
+     * </dl>
+     * </p>
+     * 
+     * @param context The context of this evaluation.
+     * @param base The bean to analyze.
+     * @return An <code>Iterator</code> containing zero or more 
+     *     <code>FeatureDescriptor</code> objects, each representing a property
+     *     on this bean, or <code>null</code> if the <code>base</code>
+     *     object is <code>null</code>.
+     */
+    public Iterator<FeatureDescriptor> getFeatureDescriptors(
+                                          ELContext context,
+                                          Object base) {
+        if (base == null){
+            return null;
+        }
+
+        BeanInfo info = null;
+        try {
+            info = Introspector.getBeanInfo(base.getClass());
+        } catch (Exception ex) {
+        }
+        if (info == null) {
+            return null;
+        }
+        ArrayList<FeatureDescriptor> list = new ArrayList<FeatureDescriptor>(
+                info.getPropertyDescriptors().length);
+        for (PropertyDescriptor pd: info.getPropertyDescriptors()) {
+            if (pd.getPropertyType() != null) {
+                pd.setValue("type", pd.getPropertyType());
+                pd.setValue("resolvableAtDesignTime", Boolean.TRUE);
+            }
+            list.add(pd);
+        }
+        return list.iterator();
+    }
+
+    /**
+     * If the base object is not <code>null</code>, returns the most 
+     * general type that this resolver accepts for the 
+     * <code>property</code> argument. Otherwise, returns <code>null</code>.
+     *
+     * <p>Assuming the base is not <code>null</code>, this method will always
+     * return <code>Object.class</code>. This is because any object is
+     * accepted as a key and is coerced into a string.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The bean to analyze.
+     * @return <code>null</code> if base is <code>null</code>; otherwise
+     *     <code>Object.class</code>.
+     */
+    public Class<?> getCommonPropertyType(ELContext context,
+                                               Object base) {
+        if (base == null){
+            return null;
+        }
+
+        return Object.class;
+    }
+
+    /*
+     * Get a public method form a public class or interface of a given method.
+     * Note that if a PropertyDescriptor is obtained for a non-public class that
+     * implements a public interface, the read/write methods will be for the
+     * class, and therefore inaccessible.  To correct this, a version of the
+     * same method must be found in a superclass or interface.
+     **/
+
+    static private Method getMethod(Class cl, Method method) {
+
+        if (Modifier.isPublic (cl.getModifiers ())) {
+            return method;
+        }
+        Class [] interfaces = cl.getInterfaces ();
+        for (int i = 0; i < interfaces.length; i++) {
+            Class c = interfaces[i];
+            Method m = null;
+            try {
+                m = c.getMethod(method.getName(), method.getParameterTypes());
+                c = m.getDeclaringClass();
+                if ((m = getMethod(c, m)) != null)
+                    return m;
+            } catch (NoSuchMethodException ex) {
+            }
+        }
+        Class c = cl.getSuperclass();
+        if (c != null) {
+            Method m = null;
+            try {
+                m = c.getMethod(method.getName(), method.getParameterTypes());
+                c = m.getDeclaringClass();
+                if ((m = getMethod(c, m)) != null)
+                    return m;
+            } catch (NoSuchMethodException ex) {
+            }
+        }
+        return null;
+    }
+
+    private BeanProperty getBeanProperty(ELContext context,
+                                         Object base,
+                                         Object prop) {
+
+        String property = prop.toString();
+        Class baseClass = base.getClass();
+        BeanProperties bps = properties.get(baseClass);
+        if (bps == null && (bps = properties2.get(baseClass)) == null) {
+            if (properties.size() > SIZE) {
+                // Discard the old map to limit the cache size.
+                properties2.clear();
+                properties2.putAll(properties);
+                properties.clear();
+            }
+            bps = new BeanProperties(baseClass);
+            properties.put(baseClass, bps);
+        }
+        return bps.getBeanProperty(property);
+    }
+}
+
diff --git a/src/org/jdesktop/el/CompositeELResolver.java b/src/org/jdesktop/el/CompositeELResolver.java
new file mode 100644
index 0000000..c80a58f
--- /dev/null
+++ b/src/org/jdesktop/el/CompositeELResolver.java
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.beans.FeatureDescriptor;
+
+/**
+ * Maintains an ordered composite list of child <code>ELResolver</code>s.
+ *
+ * <p>Though only a single <code>ELResolver</code> is associated with an
+ * <code>ELContext</code>, there are usually multiple resolvers considered
+ * for any given variable or property resolution. <code>ELResolver</code>s
+ * are combined together using a <code>CompositeELResolver</code>, to define
+ * rich semantics for evaluating an expression.</p>
+ *
+ * <p>For the {@link #getValue}, {@link #getType}, {@link #setValue} and
+ * {@link #isReadOnly} methods, an <code>ELResolver</code> is not
+ * responsible for resolving all possible (base, property) pairs. In fact,
+ * most resolvers will only handle a <code>base</code> of a single type.
+ * To indicate that a resolver has successfully resolved a particular
+ * (base, property) pair, it must set the <code>propertyResolved</code>
+ * property of the <code>ELContext</code> to <code>true</code>. If it could 
+ * not handle the given pair, it must leave this property alone. The caller
+ * must ignore the return value of the method if <code>propertyResolved</code>
+ * is <code>false</code>.</p>
+ *
+ * <p>The <code>CompositeELResolver</code> initializes the
+ * <code>ELContext.propertyResolved</code> flag to <code>false</code>, and uses 
+ * it as a stop condition for iterating through its component resolvers.</p>
+ *
+ * <p>The <code>ELContext.propertyResolved</code> flag is not used for the 
+ * design-time methods {@link #getFeatureDescriptors} and
+ * {@link #getCommonPropertyType}. Instead, results are collected and 
+ * combined from all child <code>ELResolver</code>s for these methods.</p>
+ *
+ * @see ELContext
+ * @see ELResolver
+ * @since JSP 2.1
+ */
+public class CompositeELResolver extends ELResolver {
+
+    /**
+     * Adds the given resolver to the list of component resolvers.
+     *
+     * <p>Resolvers are consulted in the order in which they are added.</p>
+     *
+     * @param elResolver The component resolver to add.
+     * @throws NullPointerException If the provided resolver is
+     *     <code>null</code>.
+     */
+    public void add(ELResolver elResolver) {
+
+        if (elResolver == null) {
+            throw new NullPointerException();
+        }
+                                                                                
+        elResolvers.add(elResolver);
+    }
+
+    /**
+     * Attempts to resolve the given <code>property</code> object on the given
+     * <code>base</code> object by querying all component resolvers.
+     *
+     * <p>If this resolver handles the given (base, property) pair, 
+     * the <code>propertyResolved</code> property of the 
+     * <code>ELContext</code> object must be set to <code>true</code>
+     * by the resolver, before returning. If this property is not 
+     * <code>true</code> after this method is called, the caller should ignore 
+     * the return value.</p>
+     *
+     * <p>First, <code>propertyResolved</code> is set to <code>false</code> on
+     * the provided <code>ELContext</code>.</p>
+     *
+     * <p>Next, for each component resolver in this composite:
+     * <ol>
+     *   <li>The <code>getValue()</code> method is called, passing in
+     *       the provided <code>context</code>, <code>base</code> and 
+     *       <code>property</code>.</li>
+     *   <li>If the <code>ELContext</code>'s <code>propertyResolved</code>
+     *       flag is <code>false</code> then iteration continues.</li>
+     *   <li>Otherwise, iteration stops and no more component resolvers are
+     *       considered. The value returned by <code>getValue()</code> is
+     *       returned by this method.</li>
+     * </ol></p>
+     *
+     * <p>If none of the component resolvers were able to perform this
+     * operation, the value <code>null</code> is returned and the
+     * <code>propertyResolved</code> flag remains set to 
+     * <code>false</code></p>.
+     *
+     * <p>Any exception thrown by component resolvers during the iteration
+     * is propagated to the caller of this method.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The base object whose property value is to be returned,
+     *     or <code>null</code> to resolve a top-level variable.
+     * @param property The property or variable to be resolved.
+     * @return If the <code>propertyResolved</code> property of 
+     *     <code>ELContext</code> was set to <code>true</code>, then
+     *     the result of the variable or property resolution; otherwise
+     *     undefined.
+     * @throws NullPointerException if context is <code>null</code>
+     * @throws PropertyNotFoundException if the given (base, property) pair
+     *     is handled by this <code>ELResolver</code> but the specified
+     *     variable or property does not exist or is not readable.
+     * @throws ELException if an exception was thrown while performing
+     *     the property or variable resolution. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     */
+    public Object getValue(ELContext context,
+                           Object base,
+                           Object property) {
+        context.setPropertyResolved(false);
+        int i = 0, len = this.elResolvers.size();
+        ELResolver elResolver;
+        Object value; 
+        while (i < len) {
+            elResolver = this.elResolvers.get(i);
+            value = elResolver.getValue(context, base, property);
+            if (context.isPropertyResolved()) {
+                return value;
+            }
+            i++;
+        } 
+        return ELContext.UNRESOLVABLE_RESULT;
+    }
+
+    /**
+     * For a given <code>base</code> and <code>property</code>, attempts to
+     * identify the most general type that is acceptable for an object to be 
+     * passed as the <code>value</code> parameter in a future call 
+     * to the {@link #setValue} method. The result is obtained by 
+     * querying all component resolvers.
+     *
+     * <p>If this resolver handles the given (base, property) pair, 
+     * the <code>propertyResolved</code> property of the 
+     * <code>ELContext</code> object must be set to <code>true</code>
+     * by the resolver, before returning. If this property is not 
+     * <code>true</code> after this method is called, the caller should ignore 
+     * the return value.</p>
+     *
+     * <p>First, <code>propertyResolved</code> is set to <code>false</code> on
+     * the provided <code>ELContext</code>.</p>
+     *
+     * <p>Next, for each component resolver in this composite:
+     * <ol>
+     *   <li>The <code>getType()</code> method is called, passing in
+     *       the provided <code>context</code>, <code>base</code> and 
+     *       <code>property</code>.</li>
+     *   <li>If the <code>ELContext</code>'s <code>propertyResolved</code>
+     *       flag is <code>false</code> then iteration continues.</li>
+     *   <li>Otherwise, iteration stops and no more component resolvers are
+     *       considered. The value returned by <code>getType()</code> is
+     *       returned by this method.</li>
+     * </ol></p>
+     *
+     * <p>If none of the component resolvers were able to perform this
+     * operation, the value <code>null</code> is returned and the
+     * <code>propertyResolved</code> flag remains set to 
+     * <code>false</code></p>.
+     *
+     * <p>Any exception thrown by component resolvers during the iteration
+     * is propagated to the caller of this method.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The base object whose property value is to be analyzed,
+     *     or <code>null</code> to analyze a top-level variable.
+     * @param property The property or variable to return the acceptable 
+     *     type for.
+     * @return If the <code>propertyResolved</code> property of 
+     *     <code>ELContext</code> was set to <code>true</code>, then
+     *     the most general acceptable type; otherwise undefined.
+     * @throws NullPointerException if context is <code>null</code>
+     * @throws PropertyNotFoundException if the given (base, property) pair
+     *     is handled by this <code>ELResolver</code> but the specified
+     *     variable or property does not exist or is not readable.
+     * @throws ELException if an exception was thrown while performing
+     *     the property or variable resolution. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     */
+    public Class<?> getType(ELContext context,
+                         Object base,
+                         Object property) {
+        context.setPropertyResolved(false);
+        int i = 0, len = this.elResolvers.size();
+        ELResolver elResolver;
+        Class<?> type;  
+        while (i < len) {
+            elResolver = this.elResolvers.get(i);
+            type = elResolver.getType(context, base, property);
+            if (context.isPropertyResolved()) {
+                return type;
+            }
+            i++;
+        }
+        return null;
+    }
+
+    /**
+     * Attempts to set the value of the given <code>property</code> 
+     * object on the given <code>base</code> object. All component
+     * resolvers are asked to attempt to set the value.
+     *
+     * <p>If this resolver handles the given (base, property) pair, 
+     * the <code>propertyResolved</code> property of the 
+     * <code>ELContext</code> object must be set to <code>true</code>
+     * by the resolver, before returning. If this property is not 
+     * <code>true</code> after this method is called, the caller can
+     * safely assume no value has been set.</p>
+     *
+     * <p>First, <code>propertyResolved</code> is set to <code>false</code> on
+     * the provided <code>ELContext</code>.</p>
+     *
+     * <p>Next, for each component resolver in this composite:
+     * <ol>
+     *   <li>The <code>setValue()</code> method is called, passing in
+     *       the provided <code>context</code>, <code>base</code>, 
+     *       <code>property</code> and <code>value</code>.</li>
+     *   <li>If the <code>ELContext</code>'s <code>propertyResolved</code>
+     *       flag is <code>false</code> then iteration continues.</li>
+     *   <li>Otherwise, iteration stops and no more component resolvers are
+     *       considered.</li>
+     * </ol></p>
+     *
+     * <p>If none of the component resolvers were able to perform this
+     * operation, the <code>propertyResolved</code> flag remains set to 
+     * <code>false</code></p>.
+     *
+     * <p>Any exception thrown by component resolvers during the iteration
+     * is propagated to the caller of this method.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The base object whose property value is to be set,
+     *     or <code>null</code> to set a top-level variable.
+     * @param property The property or variable to be set.
+     * @param val The value to set the property or variable to.
+     * @throws NullPointerException if context is <code>null</code>
+     * @throws PropertyNotFoundException if the given (base, property) pair
+     *     is handled by this <code>ELResolver</code> but the specified
+     *     variable or property does not exist.
+     * @throws PropertyNotWritableException if the given (base, property)
+     *     pair is handled by this <code>ELResolver</code> but the specified
+     *     variable or property is not writable.
+     * @throws ELException if an exception was thrown while attempting to
+     *     set the property or variable. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     */
+    public void setValue(ELContext context,
+                         Object base,
+                         Object property,
+                         Object val) {
+        context.setPropertyResolved(false);
+        int i = 0, len = this.elResolvers.size();
+        ELResolver elResolver;
+        while (i < len) {
+            elResolver = this.elResolvers.get(i);
+            elResolver.setValue(context, base, property, val);
+            if (context.isPropertyResolved()) {
+                return;
+            }
+            i++;
+        }
+    }
+
+    /**
+     * For a given <code>base</code> and <code>property</code>, attempts to
+     * determine whether a call to {@link #setValue} will always fail. The
+     * result is obtained by querying all component resolvers.
+     *
+     * <p>If this resolver handles the given (base, property) pair, 
+     * the <code>propertyResolved</code> property of the 
+     * <code>ELContext</code> object must be set to <code>true</code>
+     * by the resolver, before returning. If this property is not 
+     * <code>true</code> after this method is called, the caller should ignore 
+     * the return value.</p>
+     *
+     * <p>First, <code>propertyResolved</code> is set to <code>false</code> on
+     * the provided <code>ELContext</code>.</p>
+     *
+     * <p>Next, for each component resolver in this composite:
+     * <ol>
+     *   <li>The <code>isReadOnly()</code> method is called, passing in
+     *       the provided <code>context</code>, <code>base</code> and 
+     *       <code>property</code>.</li>
+     *   <li>If the <code>ELContext</code>'s <code>propertyResolved</code>
+     *       flag is <code>false</code> then iteration continues.</li>
+     *   <li>Otherwise, iteration stops and no more component resolvers are
+     *       considered. The value returned by <code>isReadOnly()</code> is
+     *       returned by this method.</li>
+     * </ol></p>
+     *
+     * <p>If none of the component resolvers were able to perform this
+     * operation, the value <code>false</code> is returned and the
+     * <code>propertyResolved</code> flag remains set to 
+     * <code>false</code></p>.
+     *
+     * <p>Any exception thrown by component resolvers during the iteration
+     * is propagated to the caller of this method.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The base object whose property value is to be analyzed,
+     *     or <code>null</code> to analyze a top-level variable.
+     * @param property The property or variable to return the read-only status
+     *     for.
+     * @return If the <code>propertyResolved</code> property of 
+     *     <code>ELContext</code> was set to <code>true</code>, then
+     *     <code>true</code> if the property is read-only or
+     *     <code>false</code> if not; otherwise undefined.
+     * @throws NullPointerException if context is <code>null</code>
+     * @throws PropertyNotFoundException if the given (base, property) pair
+     *     is handled by this <code>ELResolver</code> but the specified
+     *     variable or property does not exist.
+     * @throws ELException if an exception was thrown while performing
+     *     the property or variable resolution. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     */
+    public boolean isReadOnly(ELContext context,
+                              Object base,
+                              Object property) {
+        context.setPropertyResolved(false);
+        int i = 0, len = this.elResolvers.size();
+        ELResolver elResolver;
+        boolean readOnly;
+        while (i < len) {
+            elResolver = this.elResolvers.get(i);
+            readOnly = elResolver.isReadOnly(context, base, property);
+            if (context.isPropertyResolved()) {
+                return readOnly;
+            }
+            i++;
+        }
+        return false; // Does not matter
+    }
+
+    /**
+     * Returns information about the set of variables or properties that 
+     * can be resolved for the given <code>base</code> object. One use for
+     * this method is to assist tools in auto-completion. The results are
+     * collected from all component resolvers.
+     *
+     * <p>The <code>propertyResolved</code> property of the 
+     * <code>ELContext</code> is not relevant to this method.
+     * The results of all <code>ELResolver</code>s are concatenated.</p>
+     *
+     * <p>The <code>Iterator</code> returned is an iterator over the
+     * collection of <code>FeatureDescriptor</code> objects returned by
+     * the iterators returned by each component resolver's 
+     * <code>getFeatureDescriptors</code> method. If <code>null</code> is 
+     * returned by a resolver, it is skipped.</p>
+     * 
+     * @param context The context of this evaluation.
+     * @param base The base object whose set of valid properties is to
+     *     be enumerated, or <code>null</code> to enumerate the set of
+     *     top-level variables that this resolver can evaluate.
+     * @return An <code>Iterator</code> containing zero or more (possibly
+     *     infinitely more) <code>FeatureDescriptor</code> objects, or 
+     *     <code>null</code> if this resolver does not handle the given 
+     *     <code>base</code> object or that the results are too complex to 
+     *     represent with this method
+     */
+    public Iterator<FeatureDescriptor> getFeatureDescriptors(
+                                          ELContext context,
+                                          Object base) {
+        return new CompositeIterator(elResolvers.iterator(), context, base);
+    }
+
+    /**
+     * Returns the most general type that this resolver accepts for the
+     * <code>property</code> argument, given a <code>base</code> object.
+     * One use for this method is to assist tools in auto-completion. The
+     * result is obtained by querying all component resolvers.
+     *
+     * <p>The <code>Class</code> returned is the most specific class that is
+     * a common superclass of all the classes returned by each component
+     * resolver's <code>getCommonPropertyType</code> method. If 
+     * <code>null</code> is returned by a resolver, it is skipped.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The base object to return the most general property
+     *     type for, or <code>null</code> to enumerate the set of
+     *     top-level variables that this resolver can evaluate.
+     * @return <code>null</code> if this <code>ELResolver</code> does not
+     *     know how to handle the given <code>base</code> object; otherwise
+     *     <code>Object.class</code> if any type of <code>property</code>
+     *     is accepted; otherwise the most general <code>property</code>
+     *     type accepted for the given <code>base</code>.
+     */
+    public Class<?> getCommonPropertyType(ELContext context,
+                                               Object base) {
+        Class<?> commonPropertyType = null;
+        Iterator<ELResolver> iter = elResolvers.iterator();
+        while (iter.hasNext()) {
+            ELResolver elResolver = iter.next();
+            Class<?> type = elResolver.getCommonPropertyType(context, base);
+            if (type == null) {
+                // skip this EL Resolver
+                continue;
+            } else if (commonPropertyType == null) {
+                commonPropertyType = type;
+            } else if (commonPropertyType.isAssignableFrom(type)) {
+                continue;
+            } else if (type.isAssignableFrom(commonPropertyType)) {
+                commonPropertyType = type;
+            } else {
+                // Don't have a commonPropertyType
+                return null;
+            }
+        }
+        return commonPropertyType;
+    }
+
+    private final ArrayList<ELResolver> elResolvers =
+                                            new ArrayList<ELResolver>();
+
+    private static class CompositeIterator
+            implements Iterator<FeatureDescriptor> {
+
+        Iterator<ELResolver> compositeIter;
+        Iterator<FeatureDescriptor> propertyIter;
+        ELContext context;
+        Object base;
+
+        CompositeIterator(Iterator<ELResolver> iter,
+                          ELContext context,
+                          Object base) {
+            compositeIter = iter;
+            this.context = context;
+            this.base = base;
+        }
+
+        public boolean hasNext() {
+            if (propertyIter == null || !propertyIter.hasNext()) {
+                while (compositeIter.hasNext()) {
+                    ELResolver elResolver = compositeIter.next();
+                    propertyIter = elResolver.getFeatureDescriptors(
+                        context, base);
+                    if (propertyIter != null) {
+                        return propertyIter.hasNext();
+                    }
+                }
+                return false;
+            }
+            return propertyIter.hasNext();
+        }
+
+        public FeatureDescriptor next() {
+            if (propertyIter == null || !propertyIter.hasNext()) {
+                while (compositeIter.hasNext()) {
+                    ELResolver elResolver = compositeIter.next();
+                    propertyIter = elResolver.getFeatureDescriptors(
+                        context, base);
+                    if (propertyIter != null) {
+                        return propertyIter.next();
+                    }
+                }
+                return null;
+            }
+            return propertyIter.next();
+        }
+
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
+
diff --git a/src/org/jdesktop/el/ELContext.java b/src/org/jdesktop/el/ELContext.java
new file mode 100644
index 0000000..eb3316d
--- /dev/null
+++ b/src/org/jdesktop/el/ELContext.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el;
+
+import java.util.HashMap;
+import java.util.Locale;
+
+/**
+ * Context information for expression evaluation.
+ *
+ * <p>To evaluate an {@link Expression}, an <code>ELContext</code> must be
+ * provided.  The <code>ELContext</code> holds:
+ * <ul>
+ *   <li>a reference to the base {@link ELResolver} that will be consulted
+ *       to resolve model objects and their properties</li>
+ *   <li>a reference to {@link FunctionMapper} that will be used
+ *       to resolve EL Functions.
+ *   <li>a reference to {@link VariableMapper} that will be used
+ *       to resolve EL Variables.
+ *   <li>a collection of all the relevant context objects for use by 
+ *       <code>ELResolver</code>s</li>
+ *   <li>state information during the evaluation of an expression, such as
+ *       whether a property has been resolved yet</li>
+ * </ul></p>
+ *
+ * <p>The collection of context objects is necessary because each 
+ * <code>ELResolver</code> may need access to a different context object.
+ * For example, JSP and Faces resolvers need access to a 
+ * {@link javax.servlet.jsp.JspContext} and a
+ * {@link javax.faces.context.FacesContext}, respectively.</p>
+ *
+ * <p>Creation of <code>ELContext</code> objects is controlled through 
+ * the underlying technology.  For example, in JSP the
+ * <code>JspContext.getELContext()</code> factory method is used.
+ * Some technologies provide the ability to add an {@link ELContextListener}
+ * so that applications and frameworks can ensure their own context objects
+ * are attached to any newly created <code>ELContext</code>.</p>
+ *
+ * <p>Because it stores state during expression evaluation, an 
+ * <code>ELContext</code> object is not thread-safe.  Care should be taken
+ * to never share an <code>ELContext</code> instance between two or more 
+ * threads.</p>
+ *
+ * @see ELContextListener
+ * @see ELContextEvent
+ * @see ELResolver
+ * @see FunctionMapper
+ * @see VariableMapper
+ * @see javax.servlet.jsp.JspContext
+ * @since JSP 2.1
+ */
+public abstract class ELContext {
+
+    public static final Object UNRESOLVABLE_RESULT = 
+            new StringBuilder("UnresolvableResult");
+    
+    /**
+     * Called to indicate that a <code>ELResolver</code> has successfully
+     * resolved a given (base, property) pair.
+     *
+     * <p>The {@link CompositeELResolver} checks this property to determine
+     * whether it should consider or skip other component resolvers.</p>
+     *
+     * @see CompositeELResolver
+     * @param resolved true if the property has been resolved, or false if
+     *     not.
+     */
+    public void setPropertyResolved(boolean resolved) {
+        this.resolved = resolved;
+    }
+
+    /**
+     * Returns whether an {@link ELResolver} has successfully resolved a
+     * given (base, property) pair.
+     *
+     * <p>The {@link CompositeELResolver} checks this property to determine
+     * whether it should consider or skip other component resolvers.</p>
+     *
+     * @see CompositeELResolver
+     * @return true if the property has been resolved, or false if not.
+     */
+    public boolean isPropertyResolved() {
+        return resolved;
+    }
+
+    /**
+     * Associates a context object with this <code>ELContext</code>.
+     *
+     * <p>The <code>ELContext</code> maintains a collection of context objects
+     * relevant to the evaluation of an expression. These context objects
+     * are used by <code>ELResolver</code>s.  This method is used to
+     * add a context object to that collection.</p>
+     *
+     * <p>By convention, the <code>contextObject</code> will be of the
+     * type specified by the <code>key</code>.  However, this is not
+     * required and the key is used strictly as a unique identifier.</p>
+     *
+     * @param key The key used by an @{link ELResolver} to identify this
+     *     context object.
+     * @param contextObject The context object to add to the collection.
+     * @throws NullPointerException if key is null or contextObject is null.
+     */
+    public void putContext(Class key, Object contextObject) {
+        if((key == null) || (contextObject == null)) {
+            throw new NullPointerException();
+        }
+        map.put(key, contextObject);
+    }
+
+    /**
+     * Returns the context object associated with the given key.
+     *
+     * <p>The <code>ELContext</code> maintains a collection of context objects
+     * relevant to the evaluation of an expression. These context objects
+     * are used by <code>ELResolver</code>s.  This method is used to
+     * retrieve the context with the given key from the collection.</p>
+     *
+     * <p>By convention, the object returned will be of the type specified by 
+     * the <code>key</code>.  However, this is not required and the key is 
+     * used strictly as a unique identifier.</p>
+     *
+     * @param key The unique identifier that was used to associate the
+     *     context object with this <code>ELContext</code>.
+     * @return The context object associated with the given key, or null
+     *     if no such context was found.
+     * @throws NullPointerException if key is null.
+     */
+    public Object getContext(Class key) {
+        if(key == null) {
+            throw new NullPointerException();
+        }
+        return map.get(key);
+    }
+                      
+    /**
+     * Retrieves the <code>ELResolver</code> associated with this context.
+     *
+     * <p>The <code>ELContext</code> maintains a reference to the 
+     * <code>ELResolver</code> that will be consulted to resolve variables
+     * and properties during an expression evaluation.  This method
+     * retrieves the reference to the resolver.</p>
+     *
+     * <p>Once an <code>ELContext</code> is constructed, the reference to the
+     * <code>ELResolver</code> associated with the context cannot be changed.</p>
+     *
+     * @return The resolver to be consulted for variable and
+     *     property resolution during expression evaluation.
+     */
+    public abstract ELResolver getELResolver();
+    
+    /**
+     * Retrieves the <code>FunctionMapper</code> associated with this 
+     * <code>ELContext</code>.
+     *
+     * @return The function mapper to be consulted for the resolution of
+     * EL functions.
+     */
+    public abstract FunctionMapper getFunctionMapper();
+    
+    /**
+     * Holds value of property locale.
+     */
+    private Locale locale;
+    
+    /**
+     * Get the <code>Locale</code> stored by a previous invocation to 
+     * {@link #setLocale}.  If this method returns non <code>null</code>,
+     * this <code>Locale</code> must be used for all localization needs 
+     * in the implementation.  The <code>Locale</code> must not be cached
+     * to allow for applications that change <code>Locale</code> dynamically.
+     *
+     * @return The <code>Locale</code> in which this instance is operating.
+     * Used primarily for message localization.
+     */
+
+    public Locale getLocale() {
+
+        return this.locale;
+    }
+
+    /**
+     * Set the <code>Locale</code> for this instance.  This method may be 
+     * called by the party creating the instance, such as JavaServer
+     * Faces or JSP, to enable the EL implementation to provide localized
+     * messages to the user.  If no <code>Locale</code> is set, the implementation
+     * must use the locale returned by <code>Locale.getDefault( )</code>.
+     */
+    public void setLocale(Locale locale) {
+
+        this.locale = locale;
+    }    
+        
+    
+    /**
+     * Retrieves the <code>VariableMapper</code> associated with this 
+     * <code>ELContext</code>.
+     *
+     * @return The variable mapper to be consulted for the resolution of
+     * EL variables.
+     */
+    public abstract VariableMapper getVariableMapper();
+
+    private boolean resolved;
+    private HashMap map = new HashMap();
+
+
+}
+
diff --git a/src/org/jdesktop/el/ELContextEvent.java b/src/org/jdesktop/el/ELContextEvent.java
new file mode 100644
index 0000000..5012c41
--- /dev/null
+++ b/src/org/jdesktop/el/ELContextEvent.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el;
+
+/**
+ * An event which indicates that an {@link ELContext} has been created.
+ * The source object is the ELContext that was created.
+ *
+ * @see ELContext
+ * @see ELContextListener
+ * @since JSP 2.1
+ */
+public class ELContextEvent extends java.util.EventObject {
+
+    /**
+     * Constructs an ELContextEvent object to indicate that an 
+     * <code>ELContext</code> has been created.
+     *
+     * @param source the <code>ELContext</code> that was created.
+     */
+    public ELContextEvent(ELContext source) {
+        super(source);
+    }
+
+    /**
+     * Returns the <code>ELContext</code> that was created.
+     * This is a type-safe equivalent of the {@link #getSource} method.
+     *
+     * @return the ELContext that was created.
+     */
+    public ELContext getELContext() {
+        return (ELContext) getSource();
+    }
+}
diff --git a/src/org/jdesktop/el/ELContextListener.java b/src/org/jdesktop/el/ELContextListener.java
new file mode 100644
index 0000000..16346ba
--- /dev/null
+++ b/src/org/jdesktop/el/ELContextListener.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el;
+
+/**
+ * The listener interface for receiving notification when an
+ * {@link ELContext} is created.
+ *
+ * @see ELContext
+ * @see ELContextEvent
+ * @since JSP 2.1
+ */
+public interface ELContextListener extends java.util.EventListener {
+
+    /**
+     * Invoked when a new <code>ELContext</code> has been created.
+     *
+     * @param ece the notification event.
+     */
+    public void contextCreated(ELContextEvent ece);
+
+}
diff --git a/src/org/jdesktop/el/ELException.java b/src/org/jdesktop/el/ELException.java
new file mode 100644
index 0000000..c25063c
--- /dev/null
+++ b/src/org/jdesktop/el/ELException.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el;
+
+/**
+ * Represents any of the exception conditions that can arise during
+ * expression evaluation.
+ *
+ * @since JSP 2.1
+ */
+public class ELException extends RuntimeException {
+
+    //-------------------------------------
+    /**
+     * Creates an <code>ELException</code> with no detail message.
+     */
+    public ELException () {
+        super ();
+    }
+
+    //-------------------------------------
+    /**
+     * Creates an <code>ELException</code> with the provided detail message.
+     *
+     * @param pMessage the detail message
+     */
+    public ELException (String pMessage) {
+        super (pMessage);
+    }
+
+    //-------------------------------------
+    /**
+     * Creates an <code>ELException</code> with the given cause.
+     *
+     * @param pRootCause the originating cause of this exception
+     */
+    public ELException (Throwable pRootCause) {
+        super( pRootCause );
+    }
+
+    //-------------------------------------
+    /**
+     * Creates an ELException with the given detail message and root cause.
+     *
+     * @param pMessage the detail message
+     * @param pRootCause the originating cause of this exception
+     */
+    public ELException (String pMessage,
+                        Throwable pRootCause) {
+        super (pMessage, pRootCause);
+    }
+
+}
diff --git a/src/org/jdesktop/el/ELResolver.java b/src/org/jdesktop/el/ELResolver.java
new file mode 100644
index 0000000..a8135f9
--- /dev/null
+++ b/src/org/jdesktop/el/ELResolver.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el;
+
+import java.util.Iterator;
+import java.beans.FeatureDescriptor;
+
+/**
+ * Enables customization of variable and property resolution behavior for EL
+ * expression evaluation.
+ *
+ * <p>While evaluating an expression, the <code>ELResolver</code> associated
+ * with the {@link ELContext} is consulted to do the initial resolution of 
+ * the first variable of an expression. It is also consulted when a 
+ * <code>.</code> or <code>[]</code> operator is encountered, except for the
+ * last such operator in a method expression, in which case the resultion
+ * rules are hard coded.</p>
+ *
+ * <p>For example, in the EL expression <code>${employee.lastName}</code>, 
+ * the <code>ELResolver</code> determines what object <code>employee</code>
+ * refers to, and what it means to get the <code>lastName</code> property on 
+ * that object.</p>
+ *
+ * <p>Most methods in this class accept a <code>base</code> 
+ * and <code>property</code> parameter. In the case of variable resolution
+ * (e.g. determining what <code>employee</code> refers to in 
+ * <code>${employee.lastName}</code>), the <code>base</code> parameter will 
+ * be <code>null</code> and the <code>property</code> parameter will always 
+ * be of type <code>String</code>. In this case, if the <code>property</code>
+ * is not a <code>String</code>, the behavior of the <code>ELResolver</code>
+ * is undefined.</p>
+ *
+ * <p>In the case of property resolution, the <code>base</code> parameter
+ * identifies the base object and the <code>property</code> object identifies
+ * the property on that base. For example, in the expression
+ * <code>${employee.lastName}</code>, <code>base</code> is the result of the
+ * variable resolution for <code>employee</code> and <code>property</code>
+ * is the string <code>"lastName"</code>.  In the expression
+ * <code>${y[x]}</code>, <code>base</code> is the result of the variable
+ * resolution for <code>y</code> and <code>property</code> is the result of
+ * the variable resolution for <code>x</code>.</p>
+ *
+ * <p>Though only a single <code>ELResolver</code> is associated with an
+ * <code>ELContext</code>, there are usually multiple resolvers considered
+ * for any given variable or property resolution. <code>ELResolver</code>s
+ * are combined together using {@link CompositeELResolver}s, to define
+ * rich semantics for evaluating an expression.</p>
+ *
+ * <p>For the {@link #getValue}, {@link #getType}, {@link #setValue} and
+ * {@link #isReadOnly} methods, an <code>ELResolver</code> is not
+ * responsible for resolving all possible (base, property) pairs. In fact,
+ * most resolvers will only handle a <code>base</code> of a single type.
+ * To indicate that a resolver has successfully resolved a particular
+ * (base, property) pair, it must set the <code>propertyResolved</code>
+ * property of the <code>ELContext</code> to <code>true</code>. If it could 
+ * not handle the given pair, it must leave this property alone. The caller
+ * must ignore the return value of the method if <code>propertyResolved</code>
+ * is <code>false</code>.</p>
+ *
+ * <p>The {@link #getFeatureDescriptors} and {@link #getCommonPropertyType}
+ * methods are primarily designed for design-time tool support, but must
+ * handle invocation at runtime as well. The 
+ * {@link java.beans.Beans#isDesignTime} method can be used to determine 
+ * if the resolver is being consulted at design-time or runtime.</p>
+ *
+ * @see CompositeELResolver
+ * @see ELContext#getELResolver
+ * @since JSP 2.1
+ */
+public abstract class ELResolver {
+    
+    // --------------------------------------------------------- Constants
+
+    /**
+     * <p>The attribute name of the named attribute in the
+     * <code>FeatureDescriptor</code> that specifies the runtime type of
+     * the variable or property.</p>
+     */
+
+    public static final String TYPE = "type";
+
+    /**
+     * <p>The attribute name of the named attribute in the
+     * <code>FeatureDescriptor</code> that specifies whether the
+     * variable or property can be resolved at runtime.</p>
+     */
+
+    public static final String RESOLVABLE_AT_DESIGN_TIME = "resolvableAtDesignTime";
+
+    /**
+     * Attempts to resolve the given <code>property</code> object on the given
+     * <code>base</code> object.
+     *
+     * <p>If this resolver handles the given (base, property) pair, 
+     * the <code>propertyResolved</code> property of the 
+     * <code>ELContext</code> object must be set to <code>true</code>
+     * by the resolver, before returning. If this property is not 
+     * <code>true</code> after this method is called, the caller should ignore 
+     * the return value.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The base object whose property value is to be returned,
+     *     or <code>null</code> to resolve a top-level variable.
+     * @param property The property or variable to be resolved.
+     * @return If the <code>propertyResolved</code> property of 
+     *     <code>ELContext</code> was set to <code>true</code>, then
+     *     the result of the variable or property resolution; otherwise
+     *     undefined.
+     * @throws NullPointerException if context is <code>null</code>
+     * @throws PropertyNotFoundException if the given (base, property) pair
+     *     is handled by this <code>ELResolver</code> but the specified
+     *     variable or property does not exist or is not readable.
+     * @throws ELException if an exception was thrown while performing
+     *     the property or variable resolution. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     */
+    public abstract Object getValue(ELContext context,
+                                    Object base,
+                                    Object property);
+
+    /**
+     * For a given <code>base</code> and <code>property</code>, attempts to
+     * identify the most general type that is acceptable for an object to be 
+     * passed as the <code>value</code> parameter in a future call 
+     * to the {@link #setValue} method.
+     *
+     * <p>If this resolver handles the given (base, property) pair, 
+     * the <code>propertyResolved</code> property of the 
+     * <code>ELContext</code> object must be set to <code>true</code>
+     * by the resolver, before returning. If this property is not 
+     * <code>true</code> after this method is called, the caller should ignore 
+     * the return value.</p>
+     *
+     * <p>This is not always the same as <code>getValue().getClass()</code>.
+     * For example, in the case of an {@link ArrayELResolver}, the
+     * <code>getType</code> method will return the element type of the 
+     * array, which might be a superclass of the type of the actual 
+     * element that is currently in the specified array element.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The base object whose property value is to be analyzed,
+     *     or <code>null</code> to analyze a top-level variable.
+     * @param property The property or variable to return the acceptable 
+     *     type for.
+     * @return If the <code>propertyResolved</code> property of 
+     *     <code>ELContext</code> was set to <code>true</code>, then
+     *     the most general acceptable type; otherwise undefined.
+     * @throws NullPointerException if context is <code>null</code>
+     * @throws PropertyNotFoundException if the given (base, property) pair
+     *     is handled by this <code>ELResolver</code> but the specified
+     *     variable or property does not exist or is not readable.
+     * @throws ELException if an exception was thrown while performing
+     *     the property or variable resolution. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     */
+    public abstract Class<?> getType(ELContext context,
+                                  Object base,
+                                  Object property);
+
+    /**
+     * Attempts to set the value of the given <code>property</code> 
+     * object on the given <code>base</code> object.
+     *
+     * <p>If this resolver handles the given (base, property) pair, 
+     * the <code>propertyResolved</code> property of the 
+     * <code>ELContext</code> object must be set to <code>true</code>
+     * by the resolver, before returning. If this property is not 
+     * <code>true</code> after this method is called, the caller can
+     * safely assume no value has been set.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The base object whose property value is to be set,
+     *     or <code>null</code> to set a top-level variable.
+     * @param property The property or variable to be set.
+     * @param value The value to set the property or variable to.
+     * @throws NullPointerException if context is <code>null</code>
+     * @throws PropertyNotFoundException if the given (base, property) pair
+     *     is handled by this <code>ELResolver</code> but the specified
+     *     variable or property does not exist.
+     * @throws PropertyNotWritableException if the given (base, property)
+     *     pair is handled by this <code>ELResolver</code> but the specified
+     *     variable or property is not writable.
+     * @throws ELException if an exception was thrown while attempting to
+     *     set the property or variable. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     */
+    public abstract void setValue(ELContext context,
+                                  Object base,
+                                  Object property,
+                                  Object value);
+
+    /**
+     * For a given <code>base</code> and <code>property</code>, attempts to
+     * determine whether a call to {@link #setValue} will always fail.
+     *
+     * <p>If this resolver handles the given (base, property) pair, 
+     * the <code>propertyResolved</code> property of the 
+     * <code>ELContext</code> object must be set to <code>true</code>
+     * by the resolver, before returning. If this property is not 
+     * <code>true</code> after this method is called, the caller should ignore 
+     * the return value.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The base object whose property value is to be analyzed,
+     *     or <code>null</code> to analyze a top-level variable.
+     * @param property The property or variable to return the read-only status
+     *     for.
+     * @return If the <code>propertyResolved</code> property of 
+     *     <code>ELContext</code> was set to <code>true</code>, then
+     *     <code>true</code> if the property is read-only or
+     *     <code>false</code> if not; otherwise undefined.
+     * @throws NullPointerException if context is <code>null</code>
+     * @throws PropertyNotFoundException if the given (base, property) pair
+     *     is handled by this <code>ELResolver</code> but the specified
+     *     variable or property does not exist.
+     * @throws ELException if an exception was thrown while performing
+     *     the property or variable resolution. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     */
+    public abstract boolean isReadOnly(ELContext context,
+                                       Object base,
+                                       Object property);
+
+    /**
+     * Returns information about the set of variables or properties that 
+     * can be resolved for the given <code>base</code> object. One use for
+     * this method is to assist tools in auto-completion.
+     *
+     * <p>If the <code>base</code> parameter is <code>null</code>, the 
+     * resolver must enumerate the list of top-level variables it can 
+     * resolve.</p>
+     *
+     * <p>The <code>Iterator</code> returned must contain zero or more 
+     * instances of {@link java.beans.FeatureDescriptor}, in no guaranteed 
+     * order. In the case of primitive types such as <code>int</code>, the 
+     * value <code>null</code> must be returned. This is to prevent the 
+     * useless iteration through all possible primitive values. A 
+     * return value of <code>null</code> indicates that this resolver does 
+     * not handle the given <code>base</code> object or that the results 
+     * are too complex to represent with this method and the 
+     * {@link #getCommonPropertyType} method should be used instead.</p>
+     *
+     * <p>Each <code>FeatureDescriptor</code> will contain information about
+     * a single variable or property. In addition to the standard
+     * properties, the <code>FeatureDescriptor</code> must have two
+     * named attributes (as set by the <code>setValue</code> method):
+     * <ul>
+     *   <li>{@link #TYPE} - The value of this named attribute must be 
+     *       an instance of <code>java.lang.Class</code> and specify the 
+     *       runtime type of the variable or property.</li>
+     *   <li>{@link #RESOLVABLE_AT_DESIGN_TIME} - The value of this 
+     *       named attribute must be an instance of 
+     *       <code>java.lang.Boolean</code> and indicates whether it is safe 
+     *       to attempt to resolve this property at design-time. For 
+     *       instance, it may be unsafe to attempt a resolution at design 
+     *       time if the <code>ELResolver</code> needs access to a resource 
+     *       that is only available at runtime and no acceptable simulated 
+     *       value can be provided.</li>
+     * </ul></p>
+     *
+     * <p>The caller should be aware that the <code>Iterator</code> 
+     * returned might iterate through a very large or even infinitely large 
+     * set of properties. Care should be taken by the caller to not get 
+     * stuck in an infinite loop.</p>
+     *
+     * <p>This is a "best-effort" list.  Not all <code>ELResolver</code>s
+     * will return completely accurate results, but all must be callable
+     * at both design-time and runtime (i.e. whether or not
+     * <code>Beans.isDesignTime()</code> returns <code>true</code>),
+     * without causing errors.</p>
+     *
+     * <p>The <code>propertyResolved</code> property of the 
+     * <code>ELContext</code> is not relevant to this method.
+     * The results of all <code>ELResolver</code>s are concatenated
+     * in the case of composite resolvers.</p>
+     * 
+     * @param context The context of this evaluation.
+     * @param base The base object whose set of valid properties is to
+     *     be enumerated, or <code>null</code> to enumerate the set of
+     *     top-level variables that this resolver can evaluate.
+     * @return An <code>Iterator</code> containing zero or more (possibly
+     *     infinitely more) <code>FeatureDescriptor</code> objects, or 
+     *     <code>null</code> if this resolver does not handle the given 
+     *     <code>base</code> object or that the results are too complex to 
+     *     represent with this method
+     * @see java.beans.FeatureDescriptor
+     */
+    public abstract Iterator<FeatureDescriptor> getFeatureDescriptors(
+                                                   ELContext context,
+                                                   Object base);
+
+    /**
+     * Returns the most general type that this resolver accepts for the
+     * <code>property</code> argument, given a <code>base</code> object.
+     * One use for this method is to assist tools in auto-completion.
+     *
+     * <p>This assists tools in auto-completion and also provides a 
+     * way to express that the resolver accepts a primitive value, 
+     * such as an integer index into an array. For example, the 
+     * {@link ArrayELResolver} will accept any <code>int</code> as a 
+     * <code>property</code>, so the return value would be 
+     * <code>Integer.class</code>.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The base object to return the most general property
+     *     type for, or <code>null</code> to enumerate the set of
+     *     top-level variables that this resolver can evaluate.
+     * @return <code>null</code> if this <code>ELResolver</code> does not
+     *     know how to handle the given <code>base</code> object; otherwise
+     *     <code>Object.class</code> if any type of <code>property</code>
+     *     is accepted; otherwise the most general <code>property</code>
+     *     type accepted for the given <code>base</code>.
+     */
+    public abstract Class<?> getCommonPropertyType(ELContext context,
+                                                Object base);
+                    
+}
diff --git a/src/org/jdesktop/el/ELUtil.java b/src/org/jdesktop/el/ELUtil.java
new file mode 100644
index 0000000..7433eff
--- /dev/null
+++ b/src/org/jdesktop/el/ELUtil.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el;
+
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+/**
+ *
+ * <p>Utility methods for this portion of the EL implementation</p>
+ *
+ * <p>Methods on this class use a Map instance stored in ThreadLocal storage
+ * to minimize the performance impact on operations that take place multiple
+ * times on a single Thread.  The keys and values of the Map
+ * are implementation private.</p>
+ *
+ * @author edburns
+ */
+class ELUtil {
+    
+    /**
+     * <p>This class may not be constructed.</p>
+     */
+    
+    private ELUtil() {
+    }
+    
+    /**
+     * <p>The <code>ThreadLocal</code> variable used to record the
+     * {@link FacesContext} instance for each processing thread.</p>
+     */
+    private static ThreadLocal instance = new ThreadLocal() {
+            protected Object initialValue() { return (null); }
+        };
+        
+    /**
+     * @return a Map stored in ThreadLocal storage.  This may
+     * be used by methods of this class to minimize the performance
+     * impact for operations that may take place multiple times on a given
+     * Thread instance.
+     */
+
+    private static Map getCurrentInstance() {
+        Map result = (Map) instance.get();
+        if (null == result) {
+            result = new HashMap();
+            setCurrentInstance(result);
+        }
+        return result;
+
+    }
+    
+    /**
+     * <p>Replace the Map with the argument context.</p>
+     *
+     * @param context the Map to be stored in ThreadLocal storage.
+     */
+
+    private static void setCurrentInstance(Map context) {
+
+        instance.set(context);
+
+    }
+    
+    /*
+     * <p>Convenience method, calls through to 
+     * {@link #getExceptionMessageString(javax.el.ELContext,java.lang.String,Object []).
+     * </p>
+     *
+     * @param context the ELContext from which the Locale for this message
+     * is extracted.
+     *
+     * @param messageId the messageId String in the ResourceBundle
+     *
+     * @return a localized String for the argument messageId
+     */
+    
+    public static String getExceptionMessageString(ELContext context, String messageId) {
+        return getExceptionMessageString(context, messageId, null);
+    }    
+    
+    /*
+     * <p>Return a Localized message String suitable for use as an Exception message.
+     * Examine the argument <code>context</code> for a <code>Locale</code>.  If
+     * not present, use <code>Locale.getDefault()</code>.  Load the 
+     * <code>ResourceBundle</code> "javax.el.Messages" using that locale.  Get
+     * the message string for argument <code>messageId</code>.  If not found
+     * return "Missing Resource in EL implementation ??? messageId ???" 
+     * with messageId substituted with the runtime
+     * value of argument <code>messageId</code>.  If found, and argument
+     * <code>params</code> is non-null, format the message using the 
+     * params.  If formatting fails, return a sensible message including 
+     * the <code>messageId</code>.  If argument <code>params</code> is 
+     * <code>null</code>, skip formatting and return the message directly, otherwise
+     * return the formatted message.</p>
+     *
+     * @param context the ELContext from which the Locale for this message
+     * is extracted.
+     *
+     * @param messageId the messageId String in the ResourceBundle
+     *
+     * @param params parameters to the message
+     *
+     * @return a localized String for the argument messageId
+     */
+    
+    public static String getExceptionMessageString(ELContext context,
+            String messageId, 
+            Object [] params) {
+        String result = "";
+        Locale locale = null;
+        
+        if (null == context || null == messageId) {
+            return result;
+        }
+        
+        if (null == (locale = context.getLocale())) {
+            locale = Locale.getDefault();
+        }
+        if (null != locale) {
+            Map threadMap = getCurrentInstance();
+            ResourceBundle rb = null;
+            if (null == (rb = (ResourceBundle)
+            threadMap.get(locale.toString()))) {
+                rb = ResourceBundle.getBundle("org.jdesktop.el.PrivateMessages",
+                                              locale);
+                threadMap.put(locale.toString(), rb);
+            }
+            if (null != rb) {
+                try {
+                    result = rb.getString(messageId);
+                    if (null != params) {
+                        result = MessageFormat.format(result, params);
+                    }
+                } catch (IllegalArgumentException iae) {
+                    result = "Can't get localized message: parameters to message appear to be incorrect.  Message to format: " + messageId;
+                } catch (MissingResourceException mre) {
+                    result = "Missing Resource in EL implementation: ???" + messageId + "???";
+                } catch (Exception e) {
+                    result = "Exception resolving message in EL implementation: ???" + messageId + "???";
+                }
+            }
+        }
+        
+        return result;
+    }
+        
+    
+}
diff --git a/src/org/jdesktop/el/Expression.java b/src/org/jdesktop/el/Expression.java
new file mode 100644
index 0000000..e7020bc
--- /dev/null
+++ b/src/org/jdesktop/el/Expression.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * Base class for the expression subclasses {@link ValueExpression} and
+ * {@link MethodExpression}, implementing characterstics common to both.
+ *
+ * <p>All expressions must implement the <code>equals()</code> and
+ * <code>hashCode()</code> methods so that two expressions can be compared
+ * for equality. They are redefined abstract in this class to force their
+ * implementation in subclasses.</p>
+ *
+ * <p>All expressions must also be <code>Serializable</code> so that they
+ * can be saved and restored.</p>
+ *
+ * <p><code>Expression</code>s are also designed to be immutable so
+ * that only one instance needs to be created for any given expression
+ * String / {@link FunctionMapper}. This allows a container to pre-create
+ * expressions and not have to re-parse them each time they are evaluated.</p>
+ *
+ * @since JSP 2.1
+ */
+public abstract class Expression
+        implements Serializable {
+    // Debugging
+    
+    /**
+     * Returns the original String used to create this <code>Expression</code>,
+     * unmodified.
+     *
+     * <p>This is used for debugging purposes but also for the purposes
+     * of comparison (e.g. to ensure the expression in a configuration
+     * file has not changed).</p>
+     *
+     * <p>This method does not provide sufficient information to
+     * re-create an expression. Two different expressions can have exactly
+     * the same expression string but different function mappings.
+     * Serialization should be used to save and restore the state of an
+     * <code>Expression</code>.</p>
+     *
+     * @return The original expression String.
+     */
+    public abstract String getExpressionString();
+    
+    // Comparison
+    
+    /**
+     * Determines whether the specified object is equal to this
+     * <code>Expression</code>.
+     *
+     * <p>The result is <code>true</code> if and only if the argument is
+     * not <code>null</code>, is an <code>Expression</code> object that
+     * is the of the same type (<code>ValueExpression</code> or
+     * <code>MethodExpression</code>), and has an identical parsed
+     * representation.</p>
+     *
+     * <p>Note that two expressions can be equal if their expression
+     * Strings are different. For example, <code>${fn1:foo()}</code>
+     * and <code>${fn2:foo()}</code> are equal if their corresponding
+     * <code>FunctionMapper</code>s mapped <code>fn1:foo</code> and
+     * <code>fn2:foo</code> to the same method.</p>
+     *
+     * @param obj the <code>Object</code> to test for equality.
+     * @return <code>true</code> if <code>obj</code> equals this
+     *     <code>Expression</code>; <code>false</code> otherwise.
+     * @see java.util.Hashtable
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    public abstract boolean equals(Object obj);
+    
+    /**
+     * Returns the hash code for this <code>Expression</code>.
+     *
+     * <p>See the note in the {@link #equals} method on how two expressions
+     * can be equal if their expression Strings are different. Recall that
+     * if two objects are equal according to the <code>equals(Object)</code>
+     * method, then calling the <code>hashCode</code> method on each of the
+     * two objects must produce the same integer result. Implementations must
+     * take special note and implement <code>hashCode</code> correctly.</p>
+     *
+     * @return The hash code for this <code>Expression</code>.
+     * @see #equals
+     * @see java.util.Hashtable
+     * @see java.lang.Object#hashCode()
+     */
+    public abstract int hashCode();
+    
+    /**
+     * Returns whether this expression was created from only literal text.
+     *
+     * <p>This method must return <code>true</code> if and only if the
+     * expression string this expression was created from contained no
+     * unescaped EL delimeters (<code>${...}</code> or
+     * <code>#{...}</code>).</p>
+     *
+     * @return <code>true</code> if this expression was created from only
+     *     literal text; <code>false</code> otherwise.
+     */
+    public abstract boolean isLiteralText();
+    
+    
+    public static final class Result {
+        private final Type type;
+        private final Object result;
+        private final List<ResolvedProperty> resolvedProperties;
+        
+        public enum Type {
+            UNRESOLVABLE,
+            VALUE
+        }
+        
+        public Result(Type type, Object result, List<ResolvedProperty> resolvedProperties) {
+            this.type = type;
+            this.result = result;
+            this.resolvedProperties = resolvedProperties;
+            if (type == null || resolvedProperties == null) {
+                throw new NullPointerException(
+                        "Type, result and resolvedProperties must be non-null");
+            }
+        }
+        
+        public Type getType() {
+            return type;
+        }
+        
+        public Object getResult() {
+            return result;
+        }
+        
+        public List<ResolvedProperty> getResolvedProperties() {
+            // PENDING: Return a copy?
+            return resolvedProperties;
+        }
+    }
+    
+    public static final class ResolvedProperty {
+        private final Object source;
+        private final Object property;
+        
+        public ResolvedProperty(Object source, Object property) {
+            this.source = source;
+            this.property = property;
+            if (source == null || property == null) {
+                throw new IllegalArgumentException(
+                        "Source and property must be non-null");
+            }
+        }
+        
+        public Object getSource() {
+            return source;
+        }
+        
+        public Object getProperty() {
+            return property;
+        }
+        
+        public boolean equals(Object o) {
+            if (o == this) {
+                return true;
+            }
+            if (o instanceof ResolvedProperty) {
+                ResolvedProperty orp = (ResolvedProperty)o;
+                return (orp.source == source && 
+                        ((orp.property == null && property == null) ||
+                        (orp.property != null && orp.property.equals(property))));
+            }
+            return false;
+        }
+        
+        public int hashCode() {
+            int hash = 17;
+            hash = 37 * hash + source.hashCode();
+            hash = 37 * hash + property.hashCode();
+            return hash;
+        }
+    }
+
+}
+
diff --git a/src/org/jdesktop/el/ExpressionFactory.java b/src/org/jdesktop/el/ExpressionFactory.java
new file mode 100644
index 0000000..b6e37b3
--- /dev/null
+++ b/src/org/jdesktop/el/ExpressionFactory.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el;
+
+/**
+ * Parses a <code>String</code> into a {@link ValueExpression} or
+ * {@link MethodExpression} instance for later evaluation.
+ *
+ * <p>Classes that implement the EL expression language expose their
+ * functionality via this abstract class. There is no concrete implementation
+ * of this API available in this package. Technologies such as
+ * JavaServer Pages and JavaServer Faces provide access to an
+ * implementation via factory methods.</p>
+ *
+ * <p>The {@link #createValueExpression} method is used to parse expressions
+ * that evaluate to values (both l-values and r-values are supported).
+ * The {@link #createMethodExpression} method is used to parse expressions
+ * that evaluate to a reference to a method on an object.</p>
+ *
+ * <p>Unlike previous incarnations of this API, there is no way to parse
+ * and evaluate an expression in one single step. The expression needs to first
+ * be parsed, and then evaluated.</p>
+ *
+ * <p>Resolution of model objects is performed at evaluation time, via the
+ * {@link ELResolver} associated with the {@link ELContext} passed to
+ * the <code>ValueExpression</code> or <code>MethodExpression</code>.</p>
+ *
+ * <p>The ELContext object also provides access to the {@link FunctionMapper}
+ * and {@link VariableMapper} to be used when parsing the expression.
+ * EL function and variable mapping is performed at parse-time, and
+ * the results are
+ * bound to the expression. Therefore, the {@link ELContext},
+ * {@link FunctionMapper},
+ * and {@link VariableMapper}
+ * are not stored for future use and do not have to be
+ * <code>Serializable</code>.</p>
+ *
+ * <p>The <code>createValueExpression</code> and
+ * <code>createMethodExpression</code> methods must be thread-safe. That is,
+ * multiple threads may call these methods on the same
+ * <code>ExpressionFactory</code> object simultaneously. Implementations
+ * should synchronize access if they depend on transient state.
+ * Implementations should not, however, assume that only one object of
+ * each <code>ExpressionFactory</code> type will be instantiated; global
+ * caching should therefore be static.</p>
+ *
+ * <p>The <code>ExpressionFactory</code> must be able to handle the following
+ * types of input for the <code>expression</code> parameter:
+ * <ul>
+ *   <li>Single expressions using the <code>${}</code> delimiter
+ *       (e.g. <code>"${employee.lastName}"</code>).</li>
+ *   <li>Single expressions using the <code>#{}</code> delimiter
+ *       (e.g. <code>"#{employee.lastName}"</code>).</li>
+ *   <li>Literal text containing no <code>${}</code> or <code>#{}</code>
+ *       delimiters (e.g. <code>"John Doe"</code>).</li>
+ *   <li>Multiple expressions using the same delimiter (e.g.
+ *       <code>"${employee.firstName}${employee.lastName}"</code> or
+ *       <code>"#{employee.firstName}#{employee.lastName}"</code>).</li>
+ *   <li>Mixed literal text and expressions using the same delimiter (e.g.
+ *       <code>"Name: ${employee.firstName} ${employee.lastName}"</code>).</li>
+ * </ul></p>
+ *
+ * <p>The following types of input are illegal and must cause an
+ * {@link ELException} to be thrown:
+ * <ul>
+ *   <li>Multiple expressions using different delimiters (e.g.
+ *       <code>"${employee.firstName}#{employee.lastName}"</code>).</li>
+ *   <li>Mixed literal text and expressions using different delimiters(e.g.
+ *       <code>"Name: ${employee.firstName} #{employee.lastName}"</code>).</li>
+ * </ul></p>
+ *
+ * @since JSP 2.1
+ */
+public abstract class ExpressionFactory {
+    
+    /**
+     * Parses an expression into a {@link ValueExpression} for later
+     * evaluation. Use this method for expressions that refer to values.
+     *
+     * <p>This method should perform syntactic validation of the expression.
+     * If in doing so it detects errors, it should raise an
+     * <code>ELException</code>.</p>
+     *
+     * @param context The EL context used to parse the expression.
+     *     The <code>FunctionMapper</code> and <code>VariableMapper</code>
+     *     stored in the ELContext
+     *     are used to resolve functions and variables found in
+     *     the expression. They can be <code>null</code>, in which case
+     *     functions or variables are not supported for this expression.
+     *     The object
+     *     returned must invoke the same functions and access the same
+     *     variable mappings 
+     *     regardless of whether
+     *     the mappings in the provided <code>FunctionMapper</code>
+     *     and <code>VariableMapper</code> instances
+     *     change between calling
+     *     <code>ExpressionFactory.createValueExpression()</code> and any
+     *     method on <code>ValueExpression</code>.
+     *     <p>
+     *     Note that within the EL, the ${} and #{} syntaxes are treated identically.  
+     *     This includes the use of VariableMapper and FunctionMapper at expression creation 
+     *     time. Each is invoked if not null, independent 
+     *     of whether the #{} or ${} syntax is used for the expression.</p>
+     * @param expression The expression to parse
+     * @param expectedType The type the result of the expression
+     *     will be coerced to after evaluation.
+     * @return The parsed expression
+     * @throws NullPointerException Thrown if expectedType is null.
+     * @throws ELException Thrown if there are syntactical errors in the
+     *     provided expression.
+     */
+    public abstract ValueExpression createValueExpression(
+            ELContext context,
+            String expression,
+            Class<?> expectedType);
+    
+    /**
+     * Creates a ValueExpression that wraps an object instance.  This
+     * method can be used to pass any object as a ValueExpression.  The
+     * wrapper ValueExpression is read only, and returns the wrapped
+     * object via its <code>getValue()</code> method, optionally coerced.
+     *
+     * @param instance The object instance to be wrapped.
+     * @param expectedType The type the result of the expression
+     *     will be coerced to after evaluation.  There will be no
+     *     coercion if it is Object.class,
+     */
+    public abstract ValueExpression createValueExpression(
+            Object instance,
+            Class<?> expectedType);
+
+    /**
+     * Parses an expression into a {@link MethodExpression} for later
+     * evaluation. Use this method for expressions that refer to methods.
+     *
+     * <p>
+     * If the expression is a String literal, a <code>MethodExpression
+     * </code> is created, which when invoked, returns the String literal,
+     * coerced to expectedReturnType.  An ELException is thrown if
+     * expectedReturnType is void or if the coercion of the String literal
+     * to the expectedReturnType yields an error (see Section "1.16 Type
+     * Conversion").
+     * </p>
+     * <p>This method should perform syntactic validation of the expression.
+     * If in doing so it detects errors, it should raise an
+     * <code>ELException</code>.</p>
+     *
+     * @param context The EL context used to parse the expression.
+     *     The <code>FunctionMapper</code> and <code>VariableMapper</code>
+     *     stored in the ELContext
+     *     are used to resolve functions and variables found in
+     *     the expression. They can be <code>null</code>, in which
+     *     case functions or variables are not supported for this expression.
+     *     The object
+     *     returned must invoke the same functions and access the same variable
+     *     mappings
+     *     regardless of whether
+     *     the mappings in the provided <code>FunctionMapper</code>
+     *     and <code>VariableMapper</code> instances
+     *     change between calling
+     *     <code>ExpressionFactory.createMethodExpression()</code> and any
+     *     method on <code>MethodExpression</code>.
+     *     <p>
+     *     Note that within the EL, the ${} and #{} syntaxes are treated identically.  
+     *     This includes the use of VariableMapper and FunctionMapper at expression creation 
+     *     time. Each is invoked if not null, independent 
+     *     of whether the #{} or ${} syntax is used for the expression.</p>
+     *
+     * @param expression The expression to parse
+     * @param expectedReturnType The expected return type for the method
+     *     to be found. After evaluating the expression, the
+     *     <code>MethodExpression</code> must check that the return type of
+     *     the actual method matches this type. Passing in a value of
+     *     <code>null</code> indicates the caller does not care what the
+     *     return type is, and the check is disabled.
+     * @param expectedParamTypes The expected parameter types for the method to
+     *     be found. Must be an array with no elements if there are
+     *     no parameters expected. It is illegal to pass <code>null</code>.
+     * @return The parsed expression
+     * @throws ELException Thrown if there are syntactical errors in the
+     *     provided expression.
+     * @throws NullPointerException if paramTypes is <code>null</code>.
+     */
+    public abstract MethodExpression createMethodExpression(
+            ELContext context,
+            String expression,
+            Class<?> expectedReturnType,
+            Class<?>[] expectedParamTypes);
+    
+    /**
+     * Coerces an object to a specific type according to the
+     * EL type conversion rules.
+     *
+     * <p>An <code>ELException</code> is thrown if an error results from
+     * applying the conversion rules.
+     * </p>
+     *
+     * @param obj The object to coerce.
+     * @param targetType The target type for the coercion.
+     * @throws ELException thrown if an error results from applying the
+     *     conversion rules.
+     */
+    public abstract Object coerceToType(
+            Object obj,
+            Class<?> targetType);
+    
+}
+
+
diff --git a/src/org/jdesktop/el/FunctionMapper.java b/src/org/jdesktop/el/FunctionMapper.java
new file mode 100644
index 0000000..b302eb2
--- /dev/null
+++ b/src/org/jdesktop/el/FunctionMapper.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el;
+
+/**
+ * The interface to a map between EL function names and methods.
+ *
+ * <p>A <code>FunctionMapper</code> maps <code>${prefix:name()}</code> 
+ * style functions to a static method that can execute that function.</p>
+ *
+ * @since JSP 2.1
+ */
+public abstract class FunctionMapper {
+    
+  /**
+   * Resolves the specified prefix and local name into a 
+   * <code>java.lang.Method</code>.
+   *
+   * <p>Returns <code>null</code> if no function could be found that matches
+   * the given prefix and local name.</p>
+   * 
+   * @param prefix the prefix of the function, or "" if no prefix.
+   *     For example, <code>"fn"</code> in <code>${fn:method()}</code>, or
+   *     <code>""</code> in <code>${method()}</code>.
+   * @param localName the short name of the function. For example,
+   *     <code>"method"</code> in <code>${fn:method()}</code>.
+   * @return the static method to invoke, or <code>null</code> if no
+   *     match was found.
+   */
+  public abstract java.lang.reflect.Method resolveFunction(String prefix, 
+      String localName);
+  
+}
diff --git a/src/org/jdesktop/el/ListELResolver.java b/src/org/jdesktop/el/ListELResolver.java
new file mode 100644
index 0000000..5a5dd65
--- /dev/null
+++ b/src/org/jdesktop/el/ListELResolver.java
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el;
+
+import java.util.List;
+import java.util.Iterator;
+import java.util.Collections;
+import java.util.ArrayList;
+import java.beans.FeatureDescriptor;
+
+
+/**
+ * Defines property resolution behavior on instances of {@link java.util.List}.
+ *
+ * <p>This resolver handles base objects of type <code>java.util.List</code>.
+ * It accepts any object as a property and coerces that object into an
+ * integer index into the list. The resulting value is the value in the list
+ * at that index.</p>
+ *
+ * <p>This resolver can be constructed in read-only mode, which means that
+ * {@link #isReadOnly} will always return <code>true</code> and 
+ * {@link #setValue} will always throw
+ * <code>PropertyNotWritableException</code>.</p>
+ *
+ * <p><code>ELResolver</code>s are combined together using 
+ * {@link CompositeELResolver}s, to define rich semantics for evaluating 
+ * an expression. See the javadocs for {@link ELResolver} for details.</p>
+ *
+ * @see CompositeELResolver
+ * @see ELResolver
+ * @see java.util.List
+ * @since JSP 2.1
+ */
+public class ListELResolver extends ELResolver {
+
+    /**
+     * Creates a new read/write <code>ListELResolver</code>.
+     */
+    public ListELResolver() {
+        this.isReadOnly = false;
+    }
+
+    /**
+     * Creates a new <code>ListELResolver</code> whose read-only status is
+     * determined by the given parameter.
+     *
+     * @param isReadOnly <code>true</code> if this resolver cannot modify
+     *     lists; <code>false</code> otherwise.
+     */
+    public ListELResolver(boolean isReadOnly) {
+        this.isReadOnly = isReadOnly;
+    }
+
+    /**
+     * If the base object is a list, returns the most general acceptable type 
+     * for a value in this list.
+     *
+     * <p>If the base is a <code>List</code>, the <code>propertyResolved</code>
+     * property of the <code>ELContext</code> object must be set to
+     * <code>true</code> by this resolver, before returning. If this property
+     * is not <code>true</code> after this method is called, the caller 
+     * should ignore the return value.</p>
+     *
+     * <p>Assuming the base is a <code>List</code>, this method will always
+     * return <code>Object.class</code>. This is because <code>List</code>s
+     * accept any object as an element.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The list to analyze. Only bases of type <code>List</code>
+     *     are handled by this resolver.
+     * @param property The index of the element in the list to return the 
+     *     acceptable type for. Will be coerced into an integer, but 
+     *     otherwise ignored by this resolver.
+     * @return If the <code>propertyResolved</code> property of 
+     *     <code>ELContext</code> was set to <code>true</code>, then
+     *     the most general acceptable type; otherwise undefined.
+     * @throws PropertyNotFoundException if the given index is out of 
+     *     bounds for this list.
+     * @throws NullPointerException if context is <code>null</code>
+     * @throws ELException if an exception was thrown while performing
+     *     the property or variable resolution. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     */
+    public Class<?> getType(ELContext context,
+                         Object base,
+                         Object property) {
+
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base != null && base instanceof List) {
+            context.setPropertyResolved(true);
+            List list = (List) base;
+            int index = toInteger(property);
+            if (index < 0 || index >= list.size()) {
+                throw new PropertyNotFoundException();
+            } 
+            return Object.class;
+        }
+        return null;
+    }
+
+    /**
+     * If the base object is a list, returns the value at the given index.
+     * The index is specified by the <code>property</code> argument, and
+     * coerced into an integer. If the coercion could not be performed,
+     * an <code>IllegalArgumentException</code> is thrown. If the index is
+     * out of bounds, <code>null</code> is returned.
+     *
+     * <p>If the base is a <code>List</code>, the <code>propertyResolved</code>
+     * property of the <code>ELContext</code> object must be set to
+     * <code>true</code> by this resolver, before returning. If this property
+     * is not <code>true</code> after this method is called, the caller 
+     * should ignore the return value.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The list to be analyzed. Only bases of type 
+     *     <code>List</code> are handled by this resolver.
+     * @param property The index of the value to be returned. Will be coerced
+     *     into an integer.
+     * @return If the <code>propertyResolved</code> property of 
+     *     <code>ELContext</code> was set to <code>true</code>, then
+     *     the value at the given index or <code>null</code>
+     *     if the index was out of bounds. Otherwise, undefined.
+     * @throws IllegalArgumentException if the property could not be coerced
+     *     into an integer.
+     * @throws NullPointerException if context is <code>null</code>.
+     * @throws ELException if an exception was thrown while performing
+     *     the property or variable resolution. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     */
+    public Object getValue(ELContext context,
+                           Object base,
+                           Object property) {
+
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base != null && base instanceof List) {
+            context.setPropertyResolved(true);
+            List list = (List) base;
+            int index = toInteger(property);
+            if (index < 0 || index >= list.size()) {
+                return null;
+            } 
+            return list.get(index);
+        }
+        return null;
+    }
+
+    /**
+     * If the base object is a list, attempts to set the value at the
+     * given index with the given value. The index is specified by the
+     * <code>property</code> argument, and coerced into an integer. If the 
+     * coercion could not be performed, an 
+     * <code>IllegalArgumentException</code> is thrown. If the index is
+     * out of bounds, a <code>PropertyNotFoundException</code> is thrown.
+     *
+     * <p>If the base is a <code>List</code>, the <code>propertyResolved</code>
+     * property of the <code>ELContext</code> object must be set to
+     * <code>true</code> by this resolver, before returning. If this property
+     * is not <code>true</code> after this method is called, the caller 
+     * can safely assume no value was set.</p>
+     *
+     * <p>If this resolver was constructed in read-only mode, this method will
+     * always throw <code>PropertyNotWritableException</code>.</p>
+     *
+     * <p>If a <code>List</code> was created using 
+     * {@link java.util.Collections#unmodifiableList}, this method must
+     * throw <code>PropertyNotWritableException</code>. Unfortunately, 
+     * there is no Collections API method to detect this. However, an 
+     * implementation can create a prototype unmodifiable <code>List</code>
+     * and query its runtime type to see if it matches the runtime type of 
+     * the base object as a workaround.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The list to be modified. Only bases of type 
+     *     <code>List</code> are handled by this resolver.
+     * @param property The index of the value to be set. Will be coerced
+     *     into an integer.
+     * @param val The value to be set at the given index.
+     * @throws ClassCastException if the class of the specified element 
+     *     prevents it from being added to this list.
+     * @throws NullPointerException if context is <code>null</code>, or
+     *     if the value is <code>null</code> and this <code>List</code>
+     *     does not support <code>null</code> elements.
+     * @throws IllegalArgumentException if the property could not be coerced
+     *     into an integer, or if some aspect of the specified element 
+     *     prevents it from being added to this list.
+     * @throws PropertyNotWritableException if this resolver was constructed
+     *     in read-only mode, or if the set operation is not supported by 
+     *     the underlying list.
+     * @throws ELException if an exception was thrown while performing
+     *     the property or variable resolution. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     */
+    public void setValue(ELContext context,
+                         Object base,
+                         Object property,
+                         Object val) {
+
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base != null && base instanceof List) {
+            context.setPropertyResolved(true);
+            List list = (List) base;
+            int index = toInteger(property);
+            if (isReadOnly) {
+                throw new PropertyNotWritableException();
+            }
+            try {
+                list.set(index, val);
+            } catch (UnsupportedOperationException ex) {
+                throw new PropertyNotWritableException();
+            } catch (IndexOutOfBoundsException ex) {
+                throw new PropertyNotFoundException();
+            } catch (ClassCastException ex) {
+                throw ex;
+            } catch (NullPointerException ex) {
+                throw ex;
+            } catch (IllegalArgumentException ex) {
+                throw ex;
+            }
+        }
+    }
+
+    static private Class<?> theUnmodifiableListClass =
+        Collections.unmodifiableList(new ArrayList()).getClass();
+
+    /**
+     * If the base object is a list, returns whether a call to 
+     * {@link #setValue} will always fail.
+     *
+     * <p>If the base is a <code>List</code>, the <code>propertyResolved</code>
+     * property of the <code>ELContext</code> object must be set to
+     * <code>true</code> by this resolver, before returning. If this property
+     * is not <code>true</code> after this method is called, the caller 
+     * should ignore the return value.</p>
+     *
+     * <p>If this resolver was constructed in read-only mode, this method will
+     * always return <code>true</code>.</p>
+     *
+     * <p>If a <code>List</code> was created using 
+     * {@link java.util.Collections#unmodifiableList}, this method must
+     * return <code>true</code>. Unfortunately, there is no Collections API
+     * method to detect this. However, an implementation can create a
+     * prototype unmodifiable <code>List</code> and query its runtime type
+     * to see if it matches the runtime type of the base object as a 
+     * workaround.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The list to analyze. Only bases of type <code>List</code>
+     *     are handled by this resolver.
+     * @param property The index of the element in the list to return the 
+     *     acceptable type for. Will be coerced into an integer, but 
+     *     otherwise ignored by this resolver.
+     * @return If the <code>propertyResolved</code> property of 
+     *     <code>ELContext</code> was set to <code>true</code>, then
+     *     <code>true</code> if calling the <code>setValue</code> method
+     *     will always fail or <code>false</code> if it is possible that
+     *     such a call may succeed; otherwise undefined.
+     * @throws PropertyNotFoundException if the given index is out of 
+     *     bounds for this list.
+     * @throws NullPointerException if context is <code>null</code>
+     * @throws ELException if an exception was thrown while performing
+     *     the property or variable resolution. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     */
+    public boolean isReadOnly(ELContext context,
+                              Object base,
+                              Object property) {
+
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base != null && base instanceof List) {
+            context.setPropertyResolved(true);
+            List list = (List) base;
+            int index = toInteger(property);
+            if (index < 0 || index >= list.size()) {
+                throw new PropertyNotFoundException();
+            } 
+            return list.getClass() == theUnmodifiableListClass || isReadOnly;
+        }
+        return false;
+    }
+
+    /**
+     * Always returns <code>null</code>, since there is no reason to 
+     * iterate through set set of all integers.
+     *
+     * <p>The {@link #getCommonPropertyType} method returns sufficient
+     * information about what properties this resolver accepts.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The list. Only bases of type <code>List</code> are 
+     *     handled by this resolver.
+     * @return <code>null</code>.
+     */
+    public Iterator<FeatureDescriptor> getFeatureDescriptors(
+                                          ELContext context,
+                                          Object base) {
+        return null;
+    }
+
+    /**
+     * If the base object is a list, returns the most general type that 
+     * this resolver accepts for the <code>property</code> argument.
+     * Otherwise, returns <code>null</code>.
+     *
+     * <p>Assuming the base is a <code>List</code>, this method will always
+     * return <code>Integer.class</code>. This is because <code>List</code>s
+     * accept integers as their index.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The list to analyze. Only bases of type <code>List</code>
+     *     are handled by this resolver.
+     * @return <code>null</code> if base is not a <code>List</code>; otherwise
+     *     <code>Integer.class</code>.
+     */
+    public Class<?> getCommonPropertyType(ELContext context,
+                                               Object base) {
+        if (base != null && base instanceof List) {
+            return Integer.class;
+        }
+        return null;
+    }
+    
+    private int toInteger(Object p) {
+        if (p instanceof Integer) {
+            return ((Integer) p).intValue();
+        }
+        if (p instanceof Character) {
+            return ((Character) p).charValue();
+        }
+        if (p instanceof Boolean) {
+            return ((Boolean) p).booleanValue()? 1: 0;
+        }
+        if (p instanceof Number) {
+            return ((Number) p).intValue();
+        }
+        if (p instanceof String) {
+            return Integer.parseInt((String) p);
+        }
+        throw new IllegalArgumentException();
+    }
+
+    private boolean isReadOnly;
+}
+
diff --git a/src/org/jdesktop/el/MapELResolver.java b/src/org/jdesktop/el/MapELResolver.java
new file mode 100644
index 0000000..6a86683
--- /dev/null
+++ b/src/org/jdesktop/el/MapELResolver.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el;
+
+import java.beans.FeatureDescriptor;
+import java.util.Map;
+import java.util.Iterator;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Defines property resolution behavior on instances of {@link java.util.Map}.
+ *
+ * <p>This resolver handles base objects of type <code>java.util.Map</code>.
+ * It accepts any object as a property and uses that object as a key in
+ * the map. The resulting value is the value in the map that is associated with 
+ * that key.</p>
+ *
+ * <p>This resolver can be constructed in read-only mode, which means that
+ * {@link #isReadOnly} will always return <code>true</code> and 
+ * {@link #setValue} will always throw
+ * <code>PropertyNotWritableException</code>.</p>
+ *
+ * <p><code>ELResolver</code>s are combined together using 
+ * {@link CompositeELResolver}s, to define rich semantics for evaluating 
+ * an expression. See the javadocs for {@link ELResolver} for details.</p>
+ *
+ * @see CompositeELResolver
+ * @see ELResolver
+ * @see java.util.Map
+ * @since JSP 2.1
+ */
+public class MapELResolver extends ELResolver {
+
+    /**
+     * Creates a new read/write <code>MapELResolver</code>.
+     */
+    public MapELResolver() {
+        this.isReadOnly = false;
+    }
+
+    /**
+     * Creates a new <code>MapELResolver</code> whose read-only status is
+     * determined by the given parameter.
+     *
+     * @param isReadOnly <code>true</code> if this resolver cannot modify
+     *     maps; <code>false</code> otherwise.
+     */
+    public MapELResolver(boolean isReadOnly) {
+        this.isReadOnly = isReadOnly;
+    }
+
+    /**
+     * If the base object is a map, returns the most general acceptable type 
+     * for a value in this map.
+     *
+     * <p>If the base is a <code>Map</code>, the <code>propertyResolved</code>
+     * property of the <code>ELContext</code> object must be set to
+     * <code>true</code> by this resolver, before returning. If this property
+     * is not <code>true</code> after this method is called, the caller 
+     * should ignore the return value.</p>
+     *
+     * <p>Assuming the base is a <code>Map</code>, this method will always
+     * return <code>Object.class</code>. This is because <code>Map</code>s
+     * accept any object as the value for a given key.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The map to analyze. Only bases of type <code>Map</code>
+     *     are handled by this resolver.
+     * @param property The key to return the acceptable type for.
+     *     Ignored by this resolver.
+     * @return If the <code>propertyResolved</code> property of 
+     *     <code>ELContext</code> was set to <code>true</code>, then
+     *     the most general acceptable type; otherwise undefined.
+     * @throws NullPointerException if context is <code>null</code>
+     * @throws ELException if an exception was thrown while performing
+     *     the property or variable resolution. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     */
+    public Class<?> getType(ELContext context,
+                         Object base,
+                         Object property) {
+
+        if (context == null) {
+            throw new NullPointerException();
+        }
+      
+        if (base != null && base instanceof Map) {
+            context.setPropertyResolved(true);
+            return Object.class;
+        }
+        return null;
+    }
+
+    /**
+     * If the base object is a map, returns the value associated with the
+     * given key, as specified by the <code>property</code> argument. If the
+     * key was not found, <code>null</code> is returned.
+     *
+     * <p>If the base is a <code>Map</code>, the <code>propertyResolved</code>
+     * property of the <code>ELContext</code> object must be set to
+     * <code>true</code> by this resolver, before returning. If this property
+     * is not <code>true</code> after this method is called, the caller 
+     * should ignore the return value.</p>
+     *
+     * <p>Just as in {@link java.util.Map#get}, just because <code>null</code>
+     * is returned doesn't mean there is no mapping for the key; it's also
+     * possible that the <code>Map</code> explicitly maps the key to
+     * <code>null</code>.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The map to be analyzed. Only bases of type <code>Map</code>
+     *     are handled by this resolver.
+     * @param property The key whose associated value is to be returned.
+     * @return If the <code>propertyResolved</code> property of 
+     *     <code>ELContext</code> was set to <code>true</code>, then
+     *     the value associated with the given key or <code>null</code>
+     *     if the key was not found. Otherwise, undefined.
+     * @throws ClassCastException if the key is of an inappropriate type 
+     *     for this map (optionally thrown by the underlying <code>Map</code>).
+     * @throws NullPointerException if context is <code>null</code>, or if 
+     *     the key is null and this map does not permit null keys (the
+     *     latter is optionally thrown by the underlying <code>Map</code>).
+     * @throws ELException if an exception was thrown while performing
+     *     the property or variable resolution. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     */
+    public Object getValue(ELContext context,
+                           Object base,
+                           Object property) {
+
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base != null && base instanceof Map) {
+            context.setPropertyResolved(true);
+            Map map = (Map) base;
+            return map.get(property);
+        }
+        return null;
+    }
+
+    static private Class<?> theUnmodifiableMapClass =
+        Collections.unmodifiableMap(new HashMap()).getClass();
+
+    /**
+     * If the base object is a map, attempts to set the value associated with
+     * the given key, as specified by the <code>property</code> argument.
+     *
+     * <p>If the base is a <code>Map</code>, the <code>propertyResolved</code>
+     * property of the <code>ELContext</code> object must be set to
+     * <code>true</code> by this resolver, before returning. If this property
+     * is not <code>true</code> after this method is called, the caller 
+     * can safely assume no value was set.</p>
+     *
+     * <p>If this resolver was constructed in read-only mode, this method will
+     * always throw <code>PropertyNotWritableException</code>.</p>
+     *
+     * <p>If a <code>Map</code> was created using 
+     * {@link java.util.Collections#unmodifiableMap}, this method must
+     * throw <code>PropertyNotWritableException</code>. Unfortunately, 
+     * there is no Collections API method to detect this. However, an 
+     * implementation can create a prototype unmodifiable <code>Map</code> 
+     * and query its runtime type to see if it matches the runtime type of 
+     * the base object as a workaround.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The map to be modified. Only bases of type <code>Map</code>
+     *     are handled by this resolver.
+     * @param property The key with which the specified value is to be
+     *     associated.
+     * @param val The value to be associated with the specified key.
+     * @throws ClassCastException if the class of the specified key or 
+     *     value prevents it from being stored in this map.
+     * @throws NullPointerException if context is <code>null</code>, or if 
+     *     this map does not permit <code>null</code> keys or values, and
+     *     the specified key or value is <code>null</code>.
+     * @throws IllegalArgumentException if some aspect of this key or 
+     *     value prevents it from being stored in this map.
+     * @throws ELException if an exception was thrown while performing
+     *     the property or variable resolution. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     * @throws PropertyNotWritableException if this resolver was constructed
+     *     in read-only mode, or if the put operation is not supported by 
+     *     the underlying map.
+     */
+    public void setValue(ELContext context,
+                         Object base,
+                         Object property,
+                         Object val) {
+
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base != null && base instanceof Map) {
+            context.setPropertyResolved(true);
+            Map map = (Map) base;
+            if (isReadOnly || map.getClass() == theUnmodifiableMapClass) {
+                throw new PropertyNotWritableException();
+            }
+            map.put(property, val);
+        }
+    }
+
+    /**
+     * If the base object is a map, returns whether a call to 
+     * {@link #setValue} will always fail.
+     *
+     * <p>If the base is a <code>Map</code>, the <code>propertyResolved</code>
+     * property of the <code>ELContext</code> object must be set to
+     * <code>true</code> by this resolver, before returning. If this property
+     * is not <code>true</code> after this method is called, the caller 
+     * should ignore the return value.</p>
+     *
+     * <p>If this resolver was constructed in read-only mode, this method will
+     * always return <code>true</code>.</p>
+     *
+     * <p>If a <code>Map</code> was created using 
+     * {@link java.util.Collections#unmodifiableMap}, this method must
+     * return <code>true</code>. Unfortunately, there is no Collections API
+     * method to detect this. However, an implementation can create a
+     * prototype unmodifiable <code>Map</code> and query its runtime type
+     * to see if it matches the runtime type of the base object as a 
+     * workaround.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The map to analyze. Only bases of type <code>Map</code>
+     *     are handled by this resolver.
+     * @param property The key to return the read-only status for.
+     *     Ignored by this resolver.
+     * @return If the <code>propertyResolved</code> property of 
+     *     <code>ELContext</code> was set to <code>true</code>, then
+     *     <code>true</code> if calling the <code>setValue</code> method
+     *     will always fail or <code>false</code> if it is possible that
+     *     such a call may succeed; otherwise undefined.
+     * @throws NullPointerException if context is <code>null</code>
+     * @throws ELException if an exception was thrown while performing
+     *     the property or variable resolution. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     */
+    public boolean isReadOnly(ELContext context,
+                              Object base,
+                              Object property) {
+
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base != null && base instanceof Map) {
+            context.setPropertyResolved(true);
+            Map map = (Map) base;
+            return isReadOnly || map.getClass() == theUnmodifiableMapClass;
+        }
+        return false;
+    }
+
+    /**
+     * If the base object is a map, returns an <code>Iterator</code>
+     * containing the set of keys available in the <code>Map</code>. 
+     * Otherwise, returns <code>null</code>.
+     *
+     * <p>The <code>Iterator</code> returned must contain zero or more 
+     * instances of {@link java.beans.FeatureDescriptor}. Each info object 
+     * contains information about a key in the Map, and is initialized as 
+     * follows:
+     * <dl>
+     *     <li>displayName - The return value of calling the 
+     *         <code>toString</code> method on this key, or 
+     *         <code>"null"</code> if the key is <code>null</code>.</li>
+     *     <li>name - Same as displayName property.</li>
+     *     <li>shortDescription - Empty string</li>
+     *     <li>expert - <code>false</code></li>
+     *     <li>hidden - <code>false</code></li>
+     *     <li>preferred - <code>true</code></li>
+     * </dl>
+     * In addition, the following named attributes must be set in the
+     * returned <code>FeatureDescriptor</code>s:
+     * <dl>
+     *     <li>{@link ELResolver#TYPE} - The return value of calling the <code>getClass()</code>
+     *         method on this key, or <code>null</code> if the key is
+     *         <code>null</code>.</li>
+     *     <li>{@link ELResolver#RESOLVABLE_AT_DESIGN_TIME} - <code>true</code></li>
+     * </dl>
+     * </p>
+     * 
+     * @param context The context of this evaluation.
+     * @param base The map whose keys are to be iterated over. Only bases 
+     *     of type <code>Map</code> are handled by this resolver.
+     * @return An <code>Iterator</code> containing zero or more (possibly
+     *     infinitely more) <code>FeatureDescriptor</code> objects, each
+     *     representing a key in this map, or <code>null</code> if 
+     *     the base object is not a map.
+     */
+    public Iterator<FeatureDescriptor> getFeatureDescriptors(
+                                          ELContext context,
+                                          Object base) {
+
+        if (base != null && base instanceof Map) {
+            Map map = (Map) base;
+            Iterator iter = map.keySet().iterator();
+            List<FeatureDescriptor> list = new ArrayList<FeatureDescriptor>();
+            while (iter.hasNext()) {
+                Object key = iter.next();
+                FeatureDescriptor descriptor = new FeatureDescriptor();
+                String name = (key==null)? null: key.toString();
+                descriptor.setName(name);
+                descriptor.setDisplayName(name);
+                descriptor.setShortDescription("");
+                descriptor.setExpert(false);
+                descriptor.setHidden(false);
+                descriptor.setPreferred(true);
+                descriptor.setValue("type", key==null? null: key.getClass());
+                descriptor.setValue("resolvableAtDesignTime", Boolean.TRUE);
+                list.add(descriptor);
+            }
+            return list.iterator();
+        }
+        return null;
+    }
+
+    /**
+     * If the base object is a map, returns the most general type that 
+     * this resolver accepts for the <code>property</code> argument.
+     * Otherwise, returns <code>null</code>.
+     *
+     * <p>Assuming the base is a <code>Map</code>, this method will always
+     * return <code>Object.class</code>. This is because <code>Map</code>s
+     * accept any object as a key.</p>
+     *
+     * @param context The context of this evaluation.
+     * @param base The map to analyze. Only bases of type <code>Map</code>
+     *     are handled by this resolver.
+     * @return <code>null</code> if base is not a <code>Map</code>; otherwise
+     *     <code>Object.class</code>.
+     */
+    public Class<?> getCommonPropertyType(ELContext context,
+                                       Object base) {
+        if (base != null && base instanceof Map) {
+            return Object.class;
+        }
+        return null;
+    }
+
+    private boolean isReadOnly;
+}
+
diff --git a/src/org/jdesktop/el/MethodExpression.java b/src/org/jdesktop/el/MethodExpression.java
new file mode 100644
index 0000000..e963808
--- /dev/null
+++ b/src/org/jdesktop/el/MethodExpression.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el;
+
+/**
+ * An <code>Expression</code> that refers to a method on an object.
+ *
+ * <p>The {@link ExpressionFactory#createMethodExpression} method
+ * can be used to parse an expression string and return a concrete instance
+ * of <code>MethodExpression</code> that encapsulates the parsed expression.
+ * The {@link FunctionMapper} is used at parse time, not evaluation time, 
+ * so one is not needed to evaluate an expression using this class.  
+ * However, the {@link ELContext} is needed at evaluation time.</p>
+ *
+ * <p>The {@link #getMethodInfo} and {@link #invoke} methods will evaluate the 
+ * expression each time they are called. The {@link ELResolver} in the 
+ * <code>ELContext</code> is used to resolve the top-level variables and to 
+ * determine the behavior of the <code>.</code> and <code>[]</code> 
+ * operators. For any of the two methods, the {@link ELResolver#getValue} 
+ * method is used to resolve all properties up to but excluding the last 
+ * one. This provides the <code>base</code> object on which the method
+ * appears. If the <code>base</code> object is null, a 
+ * <code>PropertyNotFoundException</code> must be thrown.
+ * At the last resolution, 
+ * the final <code>property</code> is then coerced to a <code>String</code>,
+ * which provides the name of the method to be found. A method matching the 
+ * name and expected parameters provided at parse time is found and it is 
+ * either queried or invoked (depending on the method called on this
+ * <code>MethodExpression</code>).</p>
+ *
+ * <p>See the notes about comparison, serialization and immutability in 
+ * the {@link Expression} javadocs.
+ *
+ * @see ELResolver
+ * @see Expression
+ * @see ExpressionFactory
+ * @since JSP 2.1
+ */
+public abstract class MethodExpression extends Expression
+{
+    // Evaluation
+    
+    /**
+     * Evaluates the expression relative to the provided context, and
+     * returns information about the actual referenced method.
+     *
+     * @param context The context of this evaluation
+     * @return an instance of <code>MethodInfo</code> containing information
+     *     about the method the expression evaluated to.
+     * @throws NullPointerException if context is <code>null</code>
+     * @throws PropertyNotFoundException if one of the property
+     *     resolutions failed because a specified variable or property 
+     *     does not exist or is not readable.
+     * @throws MethodNotFoundException if no suitable method can be found.
+     * @throws ELException if an exception was thrown while performing
+     *     property or variable resolution. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     */
+    public abstract MethodInfo getMethodInfo(ELContext context);
+    
+    /**
+     * If a String literal is specified as the expression, returns the 
+     * String literal coerced to the expected return type of the method 
+     * signature. An <code>ELException</code> is thrown if 
+     * <code>expectedReturnType</code> is void or if the coercion of the String literal 
+     * to the <code>expectedReturnType</code> yields an error (see Section "1.16 Type
+     * Conversion" of the EL specification).
+     * 
+     * If not a String literal, evaluates the expression 
+     * relative to the provided context, invokes the method that was 
+     * found using the supplied parameters, and returns the result of 
+     * the method invocation.
+     *
+     * Any parameters passed to this method is ignored if isLiteralText()
+     * is true.
+     *
+     * @param context The context of this evaluation.
+     * @param params The parameters to pass to the method, or
+     *     <code>null</code> if no parameters.
+     * @return the result of the method invocation (<code>null</code> if
+     *     the method has a <code>void</code> return type).
+     * @throws NullPointerException if context is <code>null</code>
+     * @throws PropertyNotFoundException if one of the property
+     *     resolutions failed because a specified variable or property 
+     *     does not exist or is not readable.
+     * @throws MethodNotFoundException if no suitable method can be found.
+     * @throws ELException if a String literal is specified and
+     * expectedReturnType of the MethodExpression is void or if the coercion of the String literal 
+     * to the expectedReturnType yields an error (see Section "1.16 Type
+     * Conversion").
+     * @throws ELException if 
+     * an exception was thrown while performing
+     *     property or variable resolution. The thrown exception must be
+     *     included as the cause property of this exception, if
+     *     available.  If the exception thrown is an
+     *     <code>InvocationTargetException</code>, extract its
+     *     <code>cause</code> and pass it to the
+     *     <code>ELException</code> constructor.
+     */
+    public abstract Object invoke(ELContext context, Object[] params);
+}
diff --git a/src/org/jdesktop/el/MethodInfo.java b/src/org/jdesktop/el/MethodInfo.java
new file mode 100644
index 0000000..6951dcc
--- /dev/null
+++ b/src/org/jdesktop/el/MethodInfo.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el;
+
+/**
+ * Holds information about a method that a {@link MethodExpression} 
+ * evaluated to.
+ *
+ * @since JSP 2.1
+ */
+public class MethodInfo {
+    
+    /** 
+     * Creates a new instance of <code>MethodInfo</code> with the given
+     * information.
+     *
+     * @param name The name of the method
+     * @param returnType The return type of the method
+     * @param paramTypes The types of each of the method's parameters
+     */
+    public MethodInfo(String name, Class<?> returnType, Class<?>[] paramTypes) {
+        this.name = name;
+        this.returnType = returnType;
+        this.paramTypes = paramTypes;
+    }
+
+    /**
+     * Returns the name of the method
+     *
+     * @return the name of the method
+     */
+    public String getName() {
+        return this.name;
+    }
+    
+    /**
+     * Returns the return type of the method
+     *
+     * @return the return type of the method
+     */
+    public Class<?> getReturnType() {
+        return this.returnType;
+    }
+    
+    /**
+     * Returns the parameter types of the method
+     *
+     * @return the parameter types of the method
+     */
+    public Class<?>[] getParamTypes() {
+        return this.paramTypes;
+    }
+    
+    private String name;
+    private Class<?> returnType;
+    private Class<?>[] paramTypes;
+}
diff --git a/src/org/jdesktop/el/MethodNotFoundException.java b/src/org/jdesktop/el/MethodNotFoundException.java
new file mode 100644
index 0000000..1a945ad
--- /dev/null
+++ b/src/org/jdesktop/el/MethodNotFoundException.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el;
+
+/**
+ * Thrown when a method could not be found while evaluating a
+ * {@link MethodExpression}.
+ *
+ * @see MethodExpression
+ * @since JSP 2.1
+ */
+public class MethodNotFoundException extends ELException {
+
+    /**
+     * Creates a <code>MethodNotFoundException</code> with no detail message.
+     */
+    public MethodNotFoundException() {
+        super ();
+    }
+
+    /**
+     * Creates a <code>MethodNotFoundException</code> with the provided 
+     * detail message.
+     *
+     * @param message the detail message
+     */
+    public MethodNotFoundException(String message) {
+        super (message);
+    }
+
+    /**
+     * Creates a <code>MethodNotFoundException</code> with the given root 
+     * cause.
+     *
+     * @param exception the originating cause of this exception
+     */
+    public MethodNotFoundException(Throwable exception) {
+        super (exception);
+    }
+
+    /**
+     * Creates a <code>MethodNotFoundException</code> with the given detail
+     * message and root cause.
+     *
+     * @param pMessage the detail message
+     * @param pRootCause the originating cause of this exception
+     */
+    public MethodNotFoundException(String pMessage, Throwable pRootCause) {
+        super (pMessage, pRootCause);
+    }
+}
diff --git a/src/org/jdesktop/el/PrivateMessages.properties b/src/org/jdesktop/el/PrivateMessages.properties
new file mode 100644
index 0000000..386910d
--- /dev/null
+++ b/src/org/jdesktop/el/PrivateMessages.properties
@@ -0,0 +1,12 @@
+# Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+# subject to license terms.
+#
+
+# This properties file is essentially "package private" but because
+# there is no way to attach an access specifier to a properties file we
+# are including this comment to serve as such.
+setPropertyFailed=Can''t set property ''{0}'' on class ''{1}'' to value ''{2}''.
+propertyNotFound=The class ''{0}'' does not have the property ''{1}''.
+propertyNotreadable=The class ''{0}'' does not have a readable property ''{1}''.
+resolverNotWritable=The ELResolver for the class ''{0}'' is not writable.
+propertyNotWritable=The class ''{0}'' does not have a writable property ''{1}''.
diff --git a/src/org/jdesktop/el/PropertyNotFoundException.java b/src/org/jdesktop/el/PropertyNotFoundException.java
new file mode 100644
index 0000000..2518d3a
--- /dev/null
+++ b/src/org/jdesktop/el/PropertyNotFoundException.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el;
+
+/**
+ * Thrown when a property could not be found while evaluating a
+ * {@link ValueExpression} or {@link MethodExpression}.
+ *
+ * <p>For example, this could be triggered by an index out of bounds
+ * while setting an array value, or by an unreadable property while
+ * getting the value of a JavaBeans property.</p>
+ *
+ * @since JSP 2.1
+ */
+public class PropertyNotFoundException extends ELException {
+
+    //-------------------------------------
+    /**
+     * Creates a <code>PropertyNotFoundException</code> with no detail message.
+     */
+    public PropertyNotFoundException() {
+        super ();
+    }
+
+    //-------------------------------------
+    /**
+     * Creates a <code>PropertyNotFoundException</code> with the provided 
+     * detail message.
+     *
+     * @param message the detail message
+     */
+    public PropertyNotFoundException(String message) {
+        super (message);
+    }
+
+    /**
+     * Creates a <code>PropertyNotFoundException</code> with the given root 
+     * cause.
+     *
+     * @param exception the originating cause of this exception
+     */
+    public PropertyNotFoundException(Throwable exception) {
+        super (exception);
+    }
+
+    /**
+     * Creates a <code>PropertyNotFoundException</code> with the given detail
+     * message and root cause.
+     *
+     * @param pMessage the detail message
+     * @param pRootCause the originating cause of this exception
+     */
+    public PropertyNotFoundException(String pMessage, Throwable pRootCause) {
+        super (pMessage, pRootCause);
+    }
+
+}
diff --git a/src/org/jdesktop/el/PropertyNotWritableException.java b/src/org/jdesktop/el/PropertyNotWritableException.java
new file mode 100644
index 0000000..4cf6446
--- /dev/null
+++ b/src/org/jdesktop/el/PropertyNotWritableException.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el;
+
+
+/**
+ * Thrown when a property could not be written to while setting the
+ * value on a {@link ValueExpression}.
+ *
+ * <p>For example, this could be triggered by trying to set a map value
+ * on an unmodifiable map.</p>
+ *
+ * @since JSP 2.1
+ */
+public class PropertyNotWritableException extends ELException {
+
+    //-------------------------------------
+    /**
+     * Creates a <code>PropertyNotWritableException</code> with no detail 
+     * message.
+     */
+    public PropertyNotWritableException() {
+        super ();
+    }
+
+    //-------------------------------------
+    /**
+     * Creates a <code>PropertyNotWritableException</code> with the 
+     * provided detail message.
+     *
+     * @param pMessage the detail message
+     */
+    public PropertyNotWritableException(String pMessage) {
+        super (pMessage);
+    }
+
+    //-------------------------------------
+    /**
+     * Creates a <code>PropertyNotWritableException</code> with the given root 
+     * cause.
+     *
+     * @param exception the originating cause of this exception
+     */
+    public PropertyNotWritableException(Throwable exception) {
+        super (exception);
+    }
+
+    //-------------------------------------
+    /**
+     * Creates a <code>PropertyNotWritableException</code> with the given
+     * detail message and root cause.
+     *
+     * @param pMessage the detail message
+     * @param pRootCause the originating cause of this exception
+     */
+    public PropertyNotWritableException(String pMessage, Throwable pRootCause) {
+        super (pMessage, pRootCause);
+    }
+
+}
diff --git a/src/org/jdesktop/el/ResourceBundleELResolver.java b/src/org/jdesktop/el/ResourceBundleELResolver.java
new file mode 100644
index 0000000..8c5a984
--- /dev/null
+++ b/src/org/jdesktop/el/ResourceBundleELResolver.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el;
+
+import java.beans.FeatureDescriptor;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+/**
+ * Defines property resolution behavior on instances of
+ * {@link java.util.ResourceBundle}.
+ * 
+ * <p>
+ * This resolver handles base objects of type
+ * <code>java.util.ResourceBundle</code>. It accepts any object as a property
+ * and coerces it to a <code>java.lang.String</code> for invoking
+ * {@link java.util.ResourceBundle#getObject(java.lang.String)}.
+ * </p>
+ * 
+ * <p>
+ * This resolver is read only and will throw a
+ * {@link PropertyNotWritableException} if <code>setValue</code> is called.
+ * </p>
+ * 
+ * <p>
+ * <code>ELResolver</code>s are combined together using
+ * {@link CompositeELResolver}s, to define rich semantics for evaluating an
+ * expression. See the javadocs for {@link ELResolver} for details.
+ * </p>
+ * 
+ * @see CompositeELResolver
+ * @see ELResolver
+ * @see java.util.ResourceBundle
+ * @since JSP 2.1
+ */
+public class ResourceBundleELResolver extends ELResolver {
+
+    /**
+     * If the base object is an instance of <code>ResourceBundle</code>,
+     * the provided property will first be coerced to a <code>String</code>.
+     * The <code>Object</code> returned by <code>getObject</code> on
+     * the base <code>ResourceBundle</code> will be returned.
+     * </p>
+     * If the base is <code>ResourceBundle</code>, the
+     * <code>propertyResolved</code> property of the <code>ELContext</code>
+     * object must be set to <code>true</code> by this resolver, before
+     * returning. If this property is not <code>true</code> after this method
+     * is called, the caller should ignore the return value.
+     * </p>
+     * @param context
+     *            The context of this evaluation.
+     * @param base
+     *            The ResourceBundle to analyze.
+     * @param property
+     *            The name of the property to analyze. Will be coerced to a
+     *            <code>String</code>.
+     * @return If the <code>propertyResolved</code> property of
+     *         <code>ELContext</code> was set to <code>true</code>, then
+     *         <code>null</code> if property is <code>null</code>;
+     *         otherwise the <code>Object</code> for the given key
+     *         (property coerced to <code>String</code>) from the
+     *         <code>ResourceBundle</code>.
+     *         If no object for the given key can be found, then the 
+     *         <code>String</code> "???" + key + "???".
+     * @throws NullPointerException
+     *             if context is <code>null</code>
+     * @throws ELException
+     *             if an exception was thrown while performing the property or
+     *             variable resolution. The thrown exception must be included as
+     *             the cause property of this exception, if available.
+     */
+    public Object getValue(ELContext context, Object base, Object property) {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base instanceof ResourceBundle) {
+            context.setPropertyResolved(true);
+            if (property != null) {
+                try {
+                    return ((ResourceBundle) base).getObject(property
+                            .toString());
+                } catch (MissingResourceException e) {
+                    return "???" + property + "???";
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * If the base object is an instance of <code>ResourceBundle</code>,
+     * return <code>null</code>, since the resolver is read only.
+     * 
+     * <p>
+     * If the base is <code>ResourceBundle</code>, the
+     * <code>propertyResolved</code> property of the <code>ELContext</code>
+     * object must be set to <code>true</code> by this resolver, before
+     * returning. If this property is not <code>true</code> after this method
+     * is called, the caller should ignore the return value.
+     * </p>
+     * 
+     * @param context
+     *            The context of this evaluation.
+     * @param base
+     *            The ResourceBundle to analyze.
+     * @param property
+     *            The name of the property to analyze.
+     * @return If the <code>propertyResolved</code> property of
+     *         <code>ELContext</code> was set to <code>true</code>, then
+     *         <code>null</code>; otherwise undefined.
+     * @throws NullPointerException
+     *             if context is <code>null</code>
+     */
+    public Class<?> getType(ELContext context, Object base, Object property) {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base instanceof ResourceBundle) {
+            context.setPropertyResolved(true);
+        }
+        return null;
+    }
+
+    /**
+     * If the base object is a ResourceBundle, throw a
+     * {@link PropertyNotWritableException}.
+     * 
+     * @param context
+     *            The context of this evaluation.
+     * @param base
+     *            The ResourceBundle to be modified. Only bases that are of type
+     *            ResourceBundle are handled.
+     * @param property
+     *            The String property to use.
+     * @param value
+     *            The value to be set.
+     * @throws NullPointerException
+     *             if context is <code>null</code>.
+     * @throws PropertyNotWritableException
+     *             Always thrown if base is an instance of ReasourceBundle.
+     */
+    public void setValue(ELContext context, Object base, Object property,
+            Object value) {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+
+        if (base instanceof ResourceBundle) {
+            context.setPropertyResolved(true);
+            throw new PropertyNotWritableException(
+                    "ResourceBundles are immutable");
+        }
+    }
+
+    /**
+     * If the base object is not null and an instanceof {@link ResourceBundle},
+     * return <code>true</code>.
+     * 
+     * @param context
+     *            The context of this evaluation.
+     * @param base
+     *            The ResourceBundle to be modified. Only bases that are of type
+     *            ResourceBundle are handled.
+     * @param property
+     *            The String property to use.
+     * @return If the <code>propertyResolved</code> property of
+     *         <code>ELContext</code> was set to <code>true</code>, then
+     *         <code>true</code>; otherwise undefined.
+     * @throws NullPointerException
+     *             if context is <code>null</code>
+     */
+    public boolean isReadOnly(ELContext context, Object base, Object property) {
+        if (context == null) {
+            throw new NullPointerException();
+        }
+        if (base instanceof ResourceBundle) {
+            context.setPropertyResolved(true);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * If the base object is a ResourceBundle, returns an <code>Iterator</code>
+     * containing the set of keys available in the <code>ResourceBundle</code>.
+     * Otherwise, returns <code>null</code>.
+     * 
+     * <p>
+     * The <code>Iterator</code> returned must contain zero or more instances
+     * of {@link java.beans.FeatureDescriptor}. Each info object contains
+     * information about a key in the ResourceBundle, and is initialized as
+     * follows:
+     * <dl>
+     * <li>displayName - The <code>String</code> key
+     * <li>name - Same as displayName property.</li>
+     * <li>shortDescription - Empty string</li>
+     * <li>expert - <code>false</code></li>
+     * <li>hidden - <code>false</code></li>
+     * <li>preferred - <code>true</code></li>
+     * </dl>
+     * In addition, the following named attributes must be set in the returned
+     * <code>FeatureDescriptor</code>s:
+     * <dl>
+     * <li>{@link ELResolver#TYPE} - <code>String.class</code></li>
+     * <li>{@link ELResolver#RESOLVABLE_AT_DESIGN_TIME} - <code>true</code></li>
+     * </dl>
+     * </p>
+     * 
+     * @param context
+     *            The context of this evaluation.
+     * @param base
+     *            The bundle whose keys are to be iterated over. Only bases of
+     *            type <code>ResourceBundle</code> are handled by this
+     *            resolver.
+     * @return An <code>Iterator</code> containing zero or more (possibly
+     *         infinitely more) <code>FeatureDescriptor</code> objects, each
+     *         representing a key in this bundle, or <code>null</code> if the
+     *         base object is not a ResourceBundle.
+     */
+    public Iterator getFeatureDescriptors(ELContext context, Object base) {
+        if (base instanceof ResourceBundle) {
+            ResourceBundle bundle = (ResourceBundle) base;
+            List features = new ArrayList();
+            String key = null;
+            FeatureDescriptor desc = null;
+            for (Enumeration e = bundle.getKeys(); e.hasMoreElements();) {
+                key = (String) e.nextElement();
+                desc = new FeatureDescriptor();
+                desc.setDisplayName(key);
+                desc.setExpert(false);
+                desc.setHidden(false);
+                desc.setName(key);
+                desc.setPreferred(true);
+                desc.setValue(TYPE, String.class);
+                desc.setValue(RESOLVABLE_AT_DESIGN_TIME, Boolean.TRUE);
+                features.add(desc);
+            }
+            return features.iterator();
+        }
+        return null;
+    }
+
+    /**
+     * If the base object is a ResourceBundle, returns the most general type
+     * that this resolver accepts for the <code>property</code> argument.
+     * Otherwise, returns <code>null</code>.
+     * 
+     * <p>
+     * Assuming the base is a <code>ResourceBundle</code>, this method will
+     * always return <code>String.class</code>.
+     * 
+     * @param context
+     *            The context of this evaluation.
+     * @param base
+     *            The bundle to analyze. Only bases of type
+     *            <code>ResourceBundle</code> are handled by this resolver.
+     * @return <code>null</code> if base is not a <code>ResourceBundle</code>;
+     *         otherwise <code>String.class</code>.
+     */
+    public Class<?> getCommonPropertyType(ELContext context, Object base) {
+        if (base instanceof ResourceBundle) {
+            return String.class;
+        }
+        return null;
+    }
+}
diff --git a/src/org/jdesktop/el/ValueExpression.java b/src/org/jdesktop/el/ValueExpression.java
new file mode 100644
index 0000000..97c70b6
--- /dev/null
+++ b/src/org/jdesktop/el/ValueExpression.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * An <code>Expression</code> that can get or set a value.
+ *
+ * <p>In previous incarnations of this API, expressions could only be
+ * read. <code>ValueExpression</code> objects can now be used both to
+ * retrieve a value and to set a value. Expressions that can have a value
+ * set on them are referred to as l-value expressions. Those that
+ * cannot are referred to as r-value expressions. Not all r-value expressions
+ * can be used as l-value expressions (e.g. <code>"${1+1}"</code> or 
+ * <code>"${firstName} ${lastName}"</code>). See the EL Specification for
+ * details. Expressions that cannot be used as l-values must always 
+ * return <code>true</code> from <code>isReadOnly()</code>.</p>
+ *
+ * <p>The <code>{@link ExpressionFactory#createValueExpression}</code> method
+ * can be used to parse an expression string and return a concrete instance
+ * of <code>ValueExpression</code> that encapsulates the parsed expression.
+ * The {@link FunctionMapper} is used at parse time, not evaluation time, 
+ * so one is not needed to evaluate an expression using this class.  
+ * However, the {@link ELContext} is needed at evaluation time.</p>
+ *
+ * <p>The {@link #getValue}, {@link #setValue}, {@link #isReadOnly} and
+ * {@link #getType} methods will evaluate the expression each time they are
+ * called. The {@link ELResolver} in the <code>ELContext</code> is used to 
+ * resolve the top-level variables and to determine the behavior of the
+ * <code>.</code> and <code>[]</code> operators. For any of the four methods,
+ * the {@link ELResolver#getValue} method is used to resolve all properties 
+ * up to but excluding the last one. This provides the <code>base</code> 
+ * object. At the last resolution, the <code>ValueExpression</code> will 
+ * call the corresponding {@link ELResolver#getValue}, 
+ * {@link ELResolver#setValue}, {@link ELResolver#isReadOnly} or 
+ * {@link ELResolver#getType} method, depending on which was called on 
+ * the <code>ValueExpression</code>.
+ * </p>
+ *
+ * <p>See the notes about comparison, serialization and immutability in 
+ * the {@link Expression} javadocs.
+ *
+ * @see ELResolver
+ * @see Expression
+ * @see ExpressionFactory
+ * @since JSP 2.1
+ */
+public abstract class ValueExpression
+    extends Expression
+{
+    private Object source;
+    
+    /**
+     * Sets the source of the expression. For ValueExpressions that have a 
+     * source, any identifiers are evaluated relative to the source. For
+     * example, if the expression {@code "${first.name}"} has a source,
+     * then {@code "first"} is evaluated relative to the source.
+     *
+     * @param source the initial source for identifiers; may be {@code null}
+     */
+    public void setSource(Object source) {
+        this.source = source;
+    }
+
+    /**
+     * Returns the source of the expression.
+     *
+     * @return the source of the expression
+     */
+    public Object getSource() {
+        return source;
+    }
+    
+    /**
+     * Evaluates the expression relative to the provided context, and 
+     * returns the resulting value.
+     *
+     * <p>The resulting value is automatically coerced to the type
+     * returned by <code>getExpectedType()</code>, which was
+     * provided to the <code>ExpressionFactory</code> when this
+     * expression was created.</p>
+     *
+     * @param context The context of this evaluation.
+     * @return The result of the expression evaluation.
+     * @throws NullPointerException if context is <code>null</code>.
+     * @throws PropertyNotFoundException if one of the property
+     *     resolutions failed because a specified variable or property 
+     *     does not exist or is not readable.
+     * @throws ELException if an exception was thrown while performing
+     *     property or variable resolution. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     */
+    public abstract Object getValue(ELContext context);
+    
+    public Result getResult(ELContext context, boolean trackResolvedObjects) throws PropertyNotFoundException, 
+            ELException {
+        Object value = getValue(context);
+        List<ResolvedProperty> resolved = Collections.emptyList();
+        return new Result(Result.Type.VALUE, value, resolved);
+    }
+
+    /**
+     * Evaluates the expression relative to the provided context, and 
+     * sets the result to the provided value.
+     *
+     * @param context The context of this evaluation.
+     * @param value The new value to be set.
+     * @throws NullPointerException if context is <code>null</code>.
+     * @throws PropertyNotFoundException if one of the property
+     *     resolutions failed because a specified variable or property 
+     *     does not exist or is not readable.
+     * @throws PropertyNotWritableException if the final variable or
+     *     property resolution failed because the specified
+     *     variable or property is not writable.
+     * @throws ELException if an exception was thrown while attempting to
+     *     set the property or variable. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     */
+    public abstract void setValue(ELContext context, Object value);
+    
+    /**
+     * Evaluates the expression relative to the provided context, and 
+     * returns <code>true</code> if a call to {@link #setValue} will 
+     * always fail.
+     *
+     * @param context The context of this evaluation.
+     * @return <code>true</code> if the expression is read-only or
+     *     <code>false</code> if not.
+     * @throws NullPointerException if context is <code>null</code>.
+     * @throws PropertyNotFoundException if one of the property
+     *     resolutions failed because a specified variable or property 
+     *     does not exist or is not readable.
+     * @throws ELException if an exception was thrown while performing
+     *     property or variable resolution. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     *     * @throws NullPointerException if context is <code>null</code>
+     */
+    public abstract boolean isReadOnly(ELContext context);
+    
+    /**
+     * Evaluates the expression relative to the provided context, and 
+     * returns the most general type that is acceptable for an object to be 
+     * passed as the <code>value</code> parameter in a future call 
+     * to the {@link #setValue} method.
+     *
+     * <p>This is not always the same as <code>getValue().getClass()</code>.
+     * For example, in the case of an expression that references an 
+     * array element, the <code>getType</code> method will return the 
+     * element type of the array, which might be a superclass of the type 
+     * of the actual element that is currently in the specified 
+     * array element.</p>
+     *
+     * @param context The context of this evaluation.
+     * @return the most general acceptable type; otherwise undefined.
+     * @throws NullPointerException if context is <code>null</code>.
+     * @throws PropertyNotFoundException if one of the property
+     *     resolutions failed because a specified variable or property 
+     *     does not exist or is not readable.
+     * @throws ELException if an exception was thrown while performing
+     *     property or variable resolution. The thrown exception
+     *     must be included as the cause property of this exception, if
+     *     available.
+     */
+    public abstract Class<?> getType(ELContext context);
+    
+    /**
+     * Returns the type the result of the expression will be coerced to 
+     * after evaluation.
+     *
+     * @return the <code>expectedType</code> passed to the
+     *     <code>ExpressionFactory.createValueExpression</code> method
+     *     that created this <code>ValueExpression</code>.
+     */
+    public abstract Class<?> getExpectedType();
+}
diff --git a/src/org/jdesktop/el/VariableMapper.java b/src/org/jdesktop/el/VariableMapper.java
new file mode 100644
index 0000000..8647ca9
--- /dev/null
+++ b/src/org/jdesktop/el/VariableMapper.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el;
+
+/**
+ * The interface to a map between EL variables and the EL expressions
+ * they are associated with.
+ *
+ * @since JSP 2.1
+ */
+
+public abstract class VariableMapper {
+    
+    /**
+     * @param variable The variable name
+     * @return the ValueExpression assigned to the variable,
+     *         null if there is no previous assignment to this variable.
+     */
+    public abstract ValueExpression resolveVariable(
+            String variable);
+    
+    /**
+     * Assign a ValueExpression to an EL variable, replacing
+     * any previously assignment to the same variable.
+     * The assignment for the variable is removed if
+     * the expression is <code>null</code>.
+     *
+     * @param variable The variable name
+     * @param expression The ValueExpression to be assigned
+     *        to the variable.
+     * @return The previous ValueExpression assigned to this variable,
+     *         null if there is no previouse assignment to this variable.
+     */
+    public abstract ValueExpression setVariable(
+            String variable,
+            ValueExpression expression);
+}
diff --git a/src/org/jdesktop/el/impl/ExpressionFactoryImpl.java b/src/org/jdesktop/el/impl/ExpressionFactoryImpl.java
new file mode 100644
index 0000000..68e2d32
--- /dev/null
+++ b/src/org/jdesktop/el/impl/ExpressionFactoryImpl.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el.impl;
+
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.ExpressionFactory;
+import org.jdesktop.el.MethodExpression;
+import org.jdesktop.el.ValueExpression;
+
+import org.jdesktop.el.impl.lang.ExpressionBuilder;
+import org.jdesktop.el.impl.lang.ELSupport;
+import org.jdesktop.el.impl.util.MessageFactory;
+
+/**
+ * @see javax.el.ExpressionFactory
+ * 
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public class ExpressionFactoryImpl extends ExpressionFactory {
+
+    /**
+     * 
+     */
+    public ExpressionFactoryImpl() {
+        super();
+    }
+
+    public Object coerceToType(Object obj, Class type) {
+        return ELSupport.coerceToType(obj, type);
+    }
+
+    public MethodExpression createMethodExpression(ELContext context,
+            String expression, Class expectedReturnType,
+            Class[] expectedParamTypes) {
+        if (expectedParamTypes == null) {
+            throw new NullPointerException(MessageFactory
+                    .get("error.method.nullParms"));
+        }
+        ExpressionBuilder builder = new ExpressionBuilder(expression, context);
+        return builder.createMethodExpression(expectedReturnType,
+                expectedParamTypes);
+    }
+
+    public ValueExpression createValueExpression(ELContext context,
+            String expression, Class expectedType) {
+        if (expectedType == null) {
+            throw new NullPointerException(MessageFactory
+                    .get("error.value.expectedType"));
+        }
+        ExpressionBuilder builder = new ExpressionBuilder(expression, context);
+        return builder.createValueExpression(expectedType);
+    }
+
+    public ValueExpression createValueExpression(Object instance,
+            Class expectedType) {
+        if (expectedType == null) {
+            throw new NullPointerException(MessageFactory
+                    .get("error.value.expectedType"));
+        }
+        return new ValueExpressionLiteral(instance, expectedType);
+    }
+}
diff --git a/src/org/jdesktop/el/impl/Messages.properties b/src/org/jdesktop/el/impl/Messages.properties
new file mode 100644
index 0000000..a53c3de
--- /dev/null
+++ b/src/org/jdesktop/el/impl/Messages.properties
@@ -0,0 +1,51 @@
+# Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+# subject to license terms.
+
+# General Errors
+error.convert=Cannot convert {0} of type {1} to {2}
+error.compare=Cannot compare {0} to {1}
+error.function=Problems calling function ''{0}''
+error.unreachable.base=Target Unreachable, identifier ''{0}'' resolved to null
+error.unreachable.property=Target Unreachable, ''{0}'' returned null
+error.resolver.unhandled=ELResolver did not handle type: {0} with property of ''{1}''
+error.resolver.unhandled.null=ELResolver cannot handle a null base Object with identifier ''{0}''
+
+# ValueExpressionLiteral
+error.value.literal.write=ValueExpression is a literal and not writable: {0}
+
+# ExpressionFactoryImpl
+error.null=Expression cannot be null
+error.mixed=Expression cannot contain both '#{..}' and '${..}' : {0}
+error.method=Not a valid MethodExpression : {0}
+error.method.nullParms=Parameter types cannot be null
+error.value.expectedType=Expected type cannot be null
+
+# ExpressionMediator
+error.eval=Error Evaluating {0} : {1}
+
+# ValueSetVisitor
+error.syntax.set=Illegal Syntax for Set Operation
+
+# ReflectionUtil
+error.method.notfound=Method not found: {0}.{1}({2})
+error.property.notfound=Property ''{1}'' not found on {0}
+
+# ValidatingVisitor
+error.fnMapper.null=Expression uses functions, but no FunctionMapper was provided
+error.fnMapper.method=Function ''{0}'' not found
+error.fnMapper.paramcount=Function ''{0}'' specifies {1} params, but {2} were declared
+
+# **ExpressionImpl
+error.context.null=ELContext was null
+
+# ArrayELResolver
+error.array.outofbounds=Index {0} is out of bounds for array of size {1}
+
+# ListELResolver
+error.list.outofbounds=Index {0} is out of bounds for list of size {1}
+
+# BeanELResolver
+error.property.notfound=Property ''{1}'' not found on type: {0}
+error.property.invocation=Property ''{1}'' threw an exception from type: {0}
+error.property.notreadable=Property ''{1}'' doesn't have a 'get' specified on type: {0}
+error.property.notwritable=Property ''{1}'' doesn't have a 'set' specified on type: {0}
\ No newline at end of file
diff --git a/src/org/jdesktop/el/impl/MethodExpressionImpl.java b/src/org/jdesktop/el/impl/MethodExpressionImpl.java
new file mode 100644
index 0000000..555bd2e
--- /dev/null
+++ b/src/org/jdesktop/el/impl/MethodExpressionImpl.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el.impl;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.ELException;
+import org.jdesktop.el.ELResolver;
+import org.jdesktop.el.Expression;
+import org.jdesktop.el.ExpressionFactory;
+import org.jdesktop.el.FunctionMapper;
+import org.jdesktop.el.MethodExpression;
+import org.jdesktop.el.MethodInfo;
+import org.jdesktop.el.MethodNotFoundException;
+import org.jdesktop.el.PropertyNotFoundException;
+import org.jdesktop.el.VariableMapper;
+
+import org.jdesktop.el.impl.lang.ELSupport;
+import org.jdesktop.el.impl.lang.EvaluationContext;
+import org.jdesktop.el.impl.lang.ExpressionBuilder;
+import org.jdesktop.el.impl.parser.Node;
+import org.jdesktop.el.impl.util.ReflectionUtil;
+
+/**
+ * An <code>Expression</code> that refers to a method on an object.
+ * 
+ * <p>
+ * <code>The {@link ExpressionFactory#createMethodExpression} method
+ * can be used to parse an expression string and return a concrete instance
+ * of <code>MethodExpression</code> that encapsulates the parsed expression.
+ * The {@link FunctionMapper} is used at parse time, not evaluation time, 
+ * so one is not needed to evaluate an expression using this class.  
+ * However, the {@link ELContext} is needed at evaluation time.</p>
+ *
+ * <p>The {@link #getMethodInfo} and {@link #invoke} methods will evaluate the 
+ * expression each time they are called. The {@link ELResolver} in the 
+ * <code>ELContext</code> is used to resolve the top-level variables and to 
+ * determine the behavior of the <code>.</code> and <code>[]</code> 
+ * operators. For any of the two methods, the {@link ELResolver#getValue} 
+ * method is used to resolve all properties up to but excluding the last 
+ * one. This provides the <code>base</code> object on which the method
+ * appears. If the <code>base</code> object is null, a 
+ * <code>NullPointerException</code> must be thrown. At the last resolution, 
+ * the final <code>property</code> is then coerced to a <code>String</code>,
+ * which provides the name of the method to be found. A method matching the 
+ * name and expected parameters provided at parse time is found and it is 
+ * either queried or invoked (depending on the method called on this
+ * <code>MethodExpression</code>).</p>
+ *
+ * <p>See the notes about comparison, serialization and immutability in 
+ * the {@link Expression} javadocs.
+ *
+ * @see javax.el.ELResolver
+ * @see javax.el.Expression
+ * @see javax.el.ExpressionFactory
+ * @see javax.el.MethodExpression
+ * 
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class MethodExpressionImpl extends MethodExpression implements
+        Externalizable {
+
+    private Class expectedType;
+
+    private String expr;
+
+    private FunctionMapper fnMapper;
+
+    private VariableMapper varMapper;
+
+    private transient Node node;
+
+    private Class[] paramTypes;
+
+    /**
+     * 
+     */
+    public MethodExpressionImpl() {
+        super();
+    }
+
+    /**
+     * @param expr
+     * @param node
+     * @param fnMapper
+     * @param expectedType
+     * @param paramTypes
+     */
+    public MethodExpressionImpl(String expr, Node node,
+            FunctionMapper fnMapper, VariableMapper varMapper,
+            Class expectedType, Class[] paramTypes) {
+        super();
+        this.expr = expr;
+        this.node = node;
+        this.fnMapper = fnMapper;
+        this.varMapper = varMapper;
+        this.expectedType = expectedType;
+        this.paramTypes = paramTypes;
+    }
+
+    /**
+     * Determines whether the specified object is equal to this
+     * <code>Expression</code>.
+     * 
+     * <p>
+     * The result is <code>true</code> if and only if the argument is not
+     * <code>null</code>, is an <code>Expression</code> object that is the
+     * of the same type (<code>ValueExpression</code> or
+     * <code>MethodExpression</code>), and has an identical parsed
+     * representation.
+     * </p>
+     * 
+     * <p>
+     * Note that two expressions can be equal if their expression Strings are
+     * different. For example, <code>${fn1:foo()}</code> and
+     * <code>${fn2:foo()}</code> are equal if their corresponding
+     * <code>FunctionMapper</code>s mapped <code>fn1:foo</code> and
+     * <code>fn2:foo</code> to the same method.
+     * </p>
+     * 
+     * @param obj
+     *            the <code>Object</code> to test for equality.
+     * @return <code>true</code> if <code>obj</code> equals this
+     *         <code>Expression</code>; <code>false</code> otherwise.
+     * @see java.util.Hashtable
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    public boolean equals(Object obj) {
+        return (obj instanceof MethodExpressionImpl && obj.hashCode() == this
+                .hashCode());
+    }
+
+    /**
+     * Returns the original String used to create this <code>Expression</code>,
+     * unmodified.
+     * 
+     * <p>
+     * This is used for debugging purposes but also for the purposes of
+     * comparison (e.g. to ensure the expression in a configuration file has not
+     * changed).
+     * </p>
+     * 
+     * <p>
+     * This method does not provide sufficient information to re-create an
+     * expression. Two different expressions can have exactly the same
+     * expression string but different function mappings. Serialization should
+     * be used to save and restore the state of an <code>Expression</code>.
+     * </p>
+     * 
+     * @return The original expression String.
+     * 
+     * @see javax.el.Expression#getExpressionString()
+     */
+    public String getExpressionString() {
+        return this.expr;
+    }
+
+    /**
+     * Evaluates the expression relative to the provided context, and returns
+     * information about the actual referenced method.
+     * 
+     * @param context
+     *            The context of this evaluation
+     * @return an instance of <code>MethodInfo</code> containing information
+     *         about the method the expression evaluated to.
+     * @throws NullPointerException
+     *             if context is <code>null</code> or the base object is
+     *             <code>null</code> on the last resolution.
+     * @throws PropertyNotFoundException
+     *             if one of the property resolutions failed because a specified
+     *             variable or property does not exist or is not readable.
+     * @throws MethodNotFoundException
+     *             if no suitable method can be found.
+     * @throws ELException
+     *             if an exception was thrown while performing property or
+     *             variable resolution. The thrown exception must be included as
+     *             the cause property of this exception, if available.
+     * @see javax.el.MethodExpression#getMethodInfo(javax.el.ELContext)
+     */
+    public MethodInfo getMethodInfo(ELContext context)
+            throws PropertyNotFoundException, MethodNotFoundException,
+            ELException {
+        Node n = this.getNode();
+        EvaluationContext ctx = new EvaluationContext(context, this.fnMapper,
+                this.varMapper, this);
+        return n.getMethodInfo(ctx, this.paramTypes);
+    }
+
+    /**
+     * @return
+     * @throws ELException
+     */
+    private Node getNode() throws ELException {
+        if (this.node == null) {
+            this.node = ExpressionBuilder.createNode(this.expr);
+        }
+        return this.node;
+    }
+
+    /**
+     * Returns the hash code for this <code>Expression</code>.
+     * 
+     * <p>
+     * See the note in the {@link #equals} method on how two expressions can be
+     * equal if their expression Strings are different. Recall that if two
+     * objects are equal according to the <code>equals(Object)</code> method,
+     * then calling the <code>hashCode</code> method on each of the two
+     * objects must produce the same integer result. Implementations must take
+     * special note and implement <code>hashCode</code> correctly.
+     * </p>
+     * 
+     * @return The hash code for this <code>Expression</code>.
+     * @see #equals
+     * @see java.util.Hashtable
+     * @see java.lang.Object#hashCode()
+     */
+    public int hashCode() {
+        return this.expr.hashCode();
+    }
+
+    /**
+     * Evaluates the expression relative to the provided context, invokes the
+     * method that was found using the supplied parameters, and returns the
+     * result of the method invocation.
+     * 
+     * @param context
+     *            The context of this evaluation.
+     * @param params
+     *            The parameters to pass to the method, or <code>null</code>
+     *            if no parameters.
+     * @return the result of the method invocation (<code>null</code> if the
+     *         method has a <code>void</code> return type).
+     * @throws NullPointerException
+     *             if context is <code>null</code> or the base object is
+     *             <code>null</code> on the last resolution.
+     * @throws PropertyNotFoundException
+     *             if one of the property resolutions failed because a specified
+     *             variable or property does not exist or is not readable.
+     * @throws MethodNotFoundException
+     *             if no suitable method can be found.
+     * @throws ELException
+     *             if an exception was thrown while performing property or
+     *             variable resolution. The thrown exception must be included as
+     *             the cause property of this exception, if available. If the
+     *             exception thrown is an <code>InvocationTargetException</code>,
+     *             extract its <code>cause</code> and pass it to the
+     *             <code>ELException</code> constructor.
+     * @see javax.el.MethodExpression#invoke(javax.el.ELContext,
+     *      java.lang.Object[])
+     */
+    public Object invoke(ELContext context, Object[] params)
+            throws PropertyNotFoundException, MethodNotFoundException,
+            ELException {
+        EvaluationContext ctx = new EvaluationContext(context, this.fnMapper,
+                this.varMapper, this);
+        return this.getNode().invoke(ctx, this.paramTypes, params);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.io.Externalizable#readExternal(java.io.ObjectInput)
+     */
+    public void readExternal(ObjectInput in) throws IOException,
+            ClassNotFoundException {
+        this.expr = in.readUTF();
+        String type = in.readUTF();
+        if (!"".equals(type)) {
+            this.expectedType = ReflectionUtil.forName(type);
+        }
+        this.paramTypes = ReflectionUtil.toTypeArray(((String[]) in
+                .readObject()));
+        this.fnMapper = (FunctionMapper) in.readObject();
+        this.varMapper = (VariableMapper) in.readObject();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.io.Externalizable#writeExternal(java.io.ObjectOutput)
+     */
+    public void writeExternal(ObjectOutput out) throws IOException {
+        out.writeUTF(this.expr);
+        out.writeUTF((this.expectedType != null) ? this.expectedType.getName()
+                : "");
+        out.writeObject(ReflectionUtil.toTypeNameArray(this.paramTypes));
+        out.writeObject(this.fnMapper);
+        out.writeObject(this.varMapper);
+    }
+
+    public boolean isLiteralText() {
+        return false;
+    }
+}
diff --git a/src/org/jdesktop/el/impl/MethodExpressionLiteral.java b/src/org/jdesktop/el/impl/MethodExpressionLiteral.java
new file mode 100644
index 0000000..502259a
--- /dev/null
+++ b/src/org/jdesktop/el/impl/MethodExpressionLiteral.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el.impl;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.ELException;
+import org.jdesktop.el.MethodExpression;
+import org.jdesktop.el.MethodInfo;
+
+import org.jdesktop.el.impl.lang.ELSupport;
+import org.jdesktop.el.impl.util.ReflectionUtil;
+
+public class MethodExpressionLiteral extends MethodExpression implements Externalizable {
+
+    private Class expectedType;
+
+    private String expr;
+    
+    private Class[] paramTypes;
+    
+    public MethodExpressionLiteral() {
+        // do nothing
+    }
+    
+    public MethodExpressionLiteral(String expr, Class expectedType, Class[] paramTypes) {
+        this.expr = expr;
+        this.expectedType = expectedType;
+        this.paramTypes = paramTypes;
+    }
+
+    public MethodInfo getMethodInfo(ELContext context) throws ELException {
+        return new MethodInfo(this.expr, this.expectedType, this.paramTypes);
+    }
+
+    public Object invoke(ELContext context, Object[] params) throws ELException {
+        if (this.expectedType != null) {
+            return ELSupport.coerceToType(this.expr, this.expectedType);
+        } else {
+            return this.expr;
+        }
+    }
+
+    public String getExpressionString() {
+        return this.expr;
+    }
+
+    public boolean equals(Object obj) {
+        return (obj instanceof MethodExpressionLiteral && this.hashCode() == obj.hashCode());
+    }
+
+    public int hashCode() {
+        return this.expr.hashCode();
+    }
+
+    public boolean isLiteralText() {
+        return true;
+    }
+
+    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+        this.expr = in.readUTF();
+        String type = in.readUTF();
+        if (!"".equals(type)) {
+            this.expectedType = ReflectionUtil.forName(type);
+        }
+        this.paramTypes = ReflectionUtil.toTypeArray(((String[]) in
+                .readObject()));
+    }
+
+    public void writeExternal(ObjectOutput out) throws IOException {
+        out.writeUTF(this.expr);
+        out.writeUTF((this.expectedType != null) ? this.expectedType.getName()
+                : "");
+        out.writeObject(ReflectionUtil.toTypeNameArray(this.paramTypes));
+    }
+}
diff --git a/src/org/jdesktop/el/impl/ValueExpressionImpl.java b/src/org/jdesktop/el/impl/ValueExpressionImpl.java
new file mode 100644
index 0000000..40fbe31
--- /dev/null
+++ b/src/org/jdesktop/el/impl/ValueExpressionImpl.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el.impl;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.List;
+import java.util.Collections;
+
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.ELException;
+import org.jdesktop.el.ELResolver;
+import org.jdesktop.el.Expression;
+import org.jdesktop.el.ExpressionFactory;
+import org.jdesktop.el.FunctionMapper;
+import org.jdesktop.el.PropertyNotFoundException;
+import org.jdesktop.el.PropertyNotWritableException;
+import org.jdesktop.el.ValueExpression;
+import org.jdesktop.el.VariableMapper;
+
+import org.jdesktop.el.impl.lang.ELSupport;
+import org.jdesktop.el.impl.lang.EvaluationContext;
+import org.jdesktop.el.impl.lang.ExpressionBuilder;
+import org.jdesktop.el.impl.parser.AstLiteralExpression;
+import org.jdesktop.el.impl.parser.Node;
+import org.jdesktop.el.impl.util.ReflectionUtil;
+
+/**
+ * An <code>Expression</code> that can get or set a value.
+ * 
+ * <p>
+ * In previous incarnations of this API, expressions could only be read.
+ * <code>ValueExpression</code> objects can now be used both to retrieve a
+ * value and to set a value. Expressions that can have a value set on them are
+ * referred to as l-value expressions. Those that cannot are referred to as
+ * r-value expressions. Not all r-value expressions can be used as l-value
+ * expressions (e.g. <code>"${1+1}"</code> or
+ * <code>"${firstName} ${lastName}"</code>). See the EL Specification for
+ * details. Expressions that cannot be used as l-values must always return
+ * <code>true</code> from <code>isReadOnly()</code>.
+ * </p>
+ * 
+ * <p>
+ * <code>The {@link ExpressionFactory#createValueExpression} method
+ * can be used to parse an expression string and return a concrete instance
+ * of <code>ValueExpression</code> that encapsulates the parsed expression.
+ * The {@link FunctionMapper} is used at parse time, not evaluation time, 
+ * so one is not needed to evaluate an expression using this class.  
+ * However, the {@link ELContext} is needed at evaluation time.</p>
+ *
+ * <p>The {@link #getValue}, {@link #setValue}, {@link #isReadOnly} and
+ * {@link #getType} methods will evaluate the expression each time they are
+ * called. The {@link ELResolver} in the <code>ELContext</code> is used to 
+ * resolve the top-level variables and to determine the behavior of the
+ * <code>.</code> and <code>[]</code> operators. For any of the four methods,
+ * the {@link ELResolver#getValue} method is used to resolve all properties 
+ * up to but excluding the last one. This provides the <code>base</code> 
+ * object. At the last resolution, the <code>ValueExpression</code> will 
+ * call the corresponding {@link ELResolver#getValue}, 
+ * {@link ELResolver#setValue}, {@link ELResolver#isReadOnly} or 
+ * {@link ELResolver#getType} method, depending on which was called on 
+ * the <code>ValueExpression</code>.
+ * </p>
+ *
+ * <p>See the notes about comparison, serialization and immutability in 
+ * the {@link Expression} javadocs.
+ *
+ * @see javax.el.ELResolver
+ * @see javax.el.Expression
+ * @see javax.el.ExpressionFactory
+ * @see javax.el.ValueExpression
+ * 
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class ValueExpressionImpl extends ValueExpression implements
+        Externalizable {
+
+    private Class expectedType;
+
+    private String expr;
+
+    private FunctionMapper fnMapper;
+
+    private VariableMapper varMapper;
+
+    private transient Node node;
+
+    public ValueExpressionImpl() {
+
+    }
+
+    /**
+     * 
+     */
+    public ValueExpressionImpl(String expr, Node node, FunctionMapper fnMapper,
+            VariableMapper varMapper, Class expectedType) {
+        this.expr = expr;
+        this.node = node;
+        this.fnMapper = fnMapper;
+        this.varMapper = varMapper;
+        this.expectedType = expectedType;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    public boolean equals(Object obj) {
+        return (obj instanceof ValueExpressionImpl && obj.hashCode() == this
+                .hashCode());
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see javax.el.ValueExpression#getExpectedType()
+     */
+    public Class getExpectedType() {
+        return this.expectedType;
+    }
+
+    /**
+     * Returns the type the result of the expression will be coerced to after
+     * evaluation.
+     * 
+     * @return the <code>expectedType</code> passed to the
+     *         <code>ExpressionFactory.createValueExpression</code> method
+     *         that created this <code>ValueExpression</code>.
+     * 
+     * @see javax.el.Expression#getExpressionString()
+     */
+    public String getExpressionString() {
+        return this.expr;
+    }
+
+    /**
+     * @return
+     * @throws ELException
+     */
+    private Node getNode() throws ELException {
+        if (this.node == null) {
+            this.node = ExpressionBuilder.createNode(this.expr);
+        }
+        return this.node;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see javax.el.ValueExpression#getType(javax.el.ELContext)
+     */
+    public Class getType(ELContext context) throws PropertyNotFoundException,
+            ELException {
+        EvaluationContext ctx = new EvaluationContext(context, this.fnMapper,
+                this.varMapper, this);
+        return this.getNode().getType(ctx);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see javax.el.ValueExpression#getValue(javax.el.ELContext)
+     */
+    public Object getValue(ELContext context) throws PropertyNotFoundException,
+            ELException {
+        EvaluationContext ctx = new EvaluationContext(context, this.fnMapper,
+                this.varMapper, this);
+        Object value = this.getNode().getValue(ctx);
+        if (this.expectedType != null) {
+            return ELSupport.coerceToType(value, this.expectedType);
+        }
+        return value;
+    }
+    
+    public Result getResult(ELContext context, boolean trackResolvedObjects) throws PropertyNotFoundException, 
+            ELException {
+        EvaluationContext ctx = new EvaluationContext(context, this.fnMapper, this.varMapper, this, trackResolvedObjects);
+        Object value = this.getNode().getValue(ctx);
+
+        List<ResolvedProperty> resolvedProperties;
+
+        if (trackResolvedObjects) {
+            resolvedProperties = ctx.getResolvedProperties();
+        } else {
+            resolvedProperties = Collections.emptyList();
+        }
+
+        if (value == ELContext.UNRESOLVABLE_RESULT) {
+            return new Result(Result.Type.UNRESOLVABLE, null, resolvedProperties);
+        }
+        
+        value = ELSupport.coerceToType(value, this.expectedType);
+        return new Result(Result.Type.VALUE, value, resolvedProperties);
+    }
+    
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.lang.Object#hashCode()
+     */
+    public int hashCode() {
+        return this.expr.hashCode();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see javax.el.ValueExpression#isLiteralText()
+     */
+    public boolean isLiteralText() {
+        try {
+            return this.getNode() instanceof AstLiteralExpression;
+        } catch (ELException ele) {
+            return false;
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see javax.el.ValueExpression#isReadOnly(javax.el.ELContext)
+     */
+    public boolean isReadOnly(ELContext context)
+            throws PropertyNotFoundException, ELException {
+        EvaluationContext ctx = new EvaluationContext(context, this.fnMapper,
+                this.varMapper, this);
+        return this.getNode().isReadOnly(ctx);
+    }
+
+    public void readExternal(ObjectInput in) throws IOException,
+            ClassNotFoundException {
+        this.expr = in.readUTF();
+        String type = in.readUTF();
+        if (!"".equals(type)) {
+            this.expectedType = ReflectionUtil.forName(type);
+        }
+        this.fnMapper = (FunctionMapper) in.readObject();
+        this.varMapper = (VariableMapper) in.readObject();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see javax.el.ValueExpression#setValue(javax.el.ELContext,
+     *      java.lang.Object)
+     */
+    public void setValue(ELContext context, Object value)
+            throws PropertyNotFoundException, PropertyNotWritableException,
+            ELException {
+        EvaluationContext ctx = new EvaluationContext(context, this.fnMapper,
+                this.varMapper, this);
+        this.getNode().setValue(ctx, value);
+    }
+
+    public void writeExternal(ObjectOutput out) throws IOException {
+        out.writeUTF(this.expr);
+        out.writeUTF((this.expectedType != null) ? this.expectedType.getName()
+                : "");
+        out.writeObject(this.fnMapper);
+        out.writeObject(this.varMapper);
+    }
+
+    public String toString() {
+        return "ValueExpression["+this.expr+"]";
+    }
+}
diff --git a/src/org/jdesktop/el/impl/ValueExpressionLiteral.java b/src/org/jdesktop/el/impl/ValueExpressionLiteral.java
new file mode 100644
index 0000000..ffcf592
--- /dev/null
+++ b/src/org/jdesktop/el/impl/ValueExpressionLiteral.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el.impl;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.PropertyNotWritableException;
+
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+import org.jdesktop.el.ValueExpression;
+
+import org.jdesktop.el.impl.lang.ELSupport;
+import org.jdesktop.el.impl.util.MessageFactory;
+import org.jdesktop.el.impl.util.ReflectionUtil;
+
+public final class ValueExpressionLiteral extends ValueExpression implements
+        Externalizable {
+
+    private static final long serialVersionUID = 1L;
+
+    private Object value;
+
+    private Class expectedType;
+
+    public ValueExpressionLiteral() {
+        super();
+    }
+    
+    public ValueExpressionLiteral(Object value, Class expectedType) {
+        this.value = value;
+        this.expectedType = expectedType;
+    }
+
+    public Object getValue(ELContext context) {
+        if (this.expectedType != null) {
+            return ELSupport.coerceToType(this.value, this.expectedType);
+        }
+        return this.value;
+    }
+
+    public void setValue(ELContext context, Object value) {
+        throw new PropertyNotWritableException(MessageFactory.get(
+                "error.value.literal.write", this.value));
+    }
+
+    public boolean isReadOnly(ELContext context) {
+        return true;
+    }
+
+    public Class getType(ELContext context) {
+        return (this.value != null) ? this.value.getClass() : null;
+    }
+
+    public Class getExpectedType() {
+        return this.expectedType;
+    }
+
+    public String getExpressionString() {
+        return (this.value != null) ? this.value.toString() : null;
+    }
+
+    public boolean equals(Object obj) {
+        return (obj instanceof ValueExpressionLiteral && this
+                .equals((ValueExpressionLiteral) obj));
+    }
+
+    public boolean equals(ValueExpressionLiteral ve) {
+        return (ve != null && (this.value != null && ve.value != null && (this.value == ve.value || this.value
+                .equals(ve.value))));
+    }
+
+    public int hashCode() {
+        return (this.value != null) ? this.value.hashCode() : 0;
+    }
+
+    public boolean isLiteralText() {
+        return true;
+    }
+
+    public void writeExternal(ObjectOutput out) throws IOException {
+        out.writeObject(this.value);
+        out.writeUTF((this.expectedType != null) ? this.expectedType.getName()
+                : "");
+    }
+
+    public void readExternal(ObjectInput in) throws IOException,
+            ClassNotFoundException {
+        this.value = in.readObject();
+        String type = in.readUTF();
+        if (!"".equals(type)) {
+            this.expectedType = ReflectionUtil.forName(type);
+        }
+    }
+}
diff --git a/src/org/jdesktop/el/impl/lang/ELArithmetic.java b/src/org/jdesktop/el/impl/lang/ELArithmetic.java
new file mode 100644
index 0000000..738be66
--- /dev/null
+++ b/src/org/jdesktop/el/impl/lang/ELArithmetic.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el.impl.lang;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import org.jdesktop.el.impl.util.MessageFactory;
+
+/**
+ * A helper class of Arithmetic defined by the EL Specification
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public abstract class ELArithmetic {
+
+    public final static class BigDecimalDelegate extends ELArithmetic {
+
+        protected Number add(Number num0, Number num1) {
+            return ((BigDecimal) num0).add((BigDecimal) num1);
+        }
+
+        protected Number coerce(Number num) {
+            if (num instanceof BigDecimal)
+                return num;
+            if (num instanceof BigInteger)
+                return new BigDecimal((BigInteger) num);
+            return new BigDecimal(num.doubleValue());
+        }
+
+        protected Number coerce(String str) {
+            return new BigDecimal(str);
+        }
+
+        protected Number divide(Number num0, Number num1) {
+            return ((BigDecimal) num0).divide((BigDecimal) num1,
+                    BigDecimal.ROUND_HALF_UP);
+        }
+
+        protected Number subtract(Number num0, Number num1) {
+            return ((BigDecimal) num0).subtract((BigDecimal) num1);
+        }
+
+        protected Number mod(Number num0, Number num1) {
+            return new Double(num0.doubleValue() % num1.doubleValue());
+        }
+
+        protected Number multiply(Number num0, Number num1) {
+            return ((BigDecimal) num0).multiply((BigDecimal) num1);
+        }
+
+        public boolean matches(Object obj0, Object obj1) {
+            return (obj0 instanceof BigDecimal || obj1 instanceof BigDecimal);
+        }
+    }
+
+    public final static class BigIntegerDelegate extends ELArithmetic {
+
+        protected Number add(Number num0, Number num1) {
+            return ((BigInteger) num0).add((BigInteger) num1);
+        }
+
+        protected Number coerce(Number num) {
+            if (num instanceof BigInteger)
+                return num;
+            return new BigInteger(num.toString());
+        }
+
+        protected Number coerce(String str) {
+            return new BigInteger(str);
+        }
+
+        protected Number divide(Number num0, Number num1) {
+            return (new BigDecimal((BigInteger) num0)).divide(new BigDecimal((BigInteger) num1), BigDecimal.ROUND_HALF_UP);
+        }
+
+        protected Number multiply(Number num0, Number num1) {
+            return ((BigInteger) num0).multiply((BigInteger) num1);
+        }
+
+        protected Number mod(Number num0, Number num1) {
+            return ((BigInteger) num0).mod((BigInteger) num1);
+        }
+
+        protected Number subtract(Number num0, Number num1) {
+            return ((BigInteger) num0).subtract((BigInteger) num1);
+        }
+
+        public boolean matches(Object obj0, Object obj1) {
+            return (obj0 instanceof BigInteger || obj1 instanceof BigInteger);
+        }
+    }
+
+    public final static class DoubleDelegate extends ELArithmetic {
+
+        protected Number add(Number num0, Number num1) {
+        	// could only be one of these
+        	if (num0 instanceof BigDecimal) {
+        		return ((BigDecimal) num0).add(new BigDecimal(num1.doubleValue()));
+        	} else if (num1 instanceof BigDecimal) {
+        		return ((new BigDecimal(num0.doubleValue()).add((BigDecimal) num1)));
+        	}
+            return new Double(num0.doubleValue() + num1.doubleValue());
+        }
+
+        protected Number coerce(Number num) {
+            if (num instanceof Double)
+                return num;
+            if (num instanceof BigInteger)
+            	return new BigDecimal((BigInteger) num);
+            return new Double(num.doubleValue());
+        }
+
+        protected Number coerce(String str) {
+            return new Double(str);
+        }
+
+        protected Number divide(Number num0, Number num1) {
+            return new Double(num0.doubleValue() / num1.doubleValue());
+        }
+
+        protected Number mod(Number num0, Number num1) {
+            return new Double(num0.doubleValue() % num1.doubleValue());
+        }
+
+        protected Number subtract(Number num0, Number num1) {
+        	// could only be one of these
+        	if (num0 instanceof BigDecimal) {
+        		return ((BigDecimal) num0).subtract(new BigDecimal(num1.doubleValue()));
+        	} else if (num1 instanceof BigDecimal) {
+        		return ((new BigDecimal(num0.doubleValue()).subtract((BigDecimal) num1)));
+        	}
+            return new Double(num0.doubleValue() - num1.doubleValue());
+        }
+
+        protected Number multiply(Number num0, Number num1) {
+        	// could only be one of these
+        	if (num0 instanceof BigDecimal) {
+        		return ((BigDecimal) num0).multiply(new BigDecimal(num1.doubleValue()));
+        	} else if (num1 instanceof BigDecimal) {
+        		return ((new BigDecimal(num0.doubleValue()).multiply((BigDecimal) num1)));
+        	}
+            return new Double(num0.doubleValue() * num1.doubleValue());
+        }
+
+        public boolean matches(Object obj0, Object obj1) {
+            return (obj0 instanceof Double
+                    || obj1 instanceof Double
+                    || obj0 instanceof Float
+                    || obj1 instanceof Float
+                    || (obj0 != null && (Double.TYPE == obj0.getClass() || Float.TYPE == obj0.getClass()))
+                    || (obj1 != null && (Double.TYPE == obj1.getClass() || Float.TYPE == obj1.getClass()))
+                    || (obj0 instanceof String && ELSupport
+                            .isStringFloat((String) obj0)) || (obj1 instanceof String && ELSupport
+                    .isStringFloat((String) obj1)));
+        }
+    }
+
+    public final static class LongDelegate extends ELArithmetic {
+
+        protected Number add(Number num0, Number num1) {
+            return new Long(num0.longValue() + num1.longValue());
+        }
+
+        protected Number coerce(Number num) {
+            if (num instanceof Long)
+                return num;
+            return new Long(num.longValue());
+        }
+
+        protected Number coerce(String str) {
+            return new Long(str);
+        }
+
+        protected Number divide(Number num0, Number num1) {
+            return new Long(num0.longValue() / num1.longValue());
+        }
+
+        protected Number mod(Number num0, Number num1) {
+            return new Long(num0.longValue() % num1.longValue());
+        }
+
+        protected Number subtract(Number num0, Number num1) {
+            return new Long(num0.longValue() - num1.longValue());
+        }
+
+        protected Number multiply(Number num0, Number num1) {
+            return new Long(num0.longValue() * num1.longValue());
+        }
+
+        public boolean matches(Object obj0, Object obj1) {
+            return (obj0 instanceof Long || obj1 instanceof Long);
+        }
+    }
+
+    public final static BigDecimalDelegate BIGDECIMAL = new BigDecimalDelegate();
+
+    public final static BigIntegerDelegate BIGINTEGER = new BigIntegerDelegate();
+
+    public final static DoubleDelegate DOUBLE = new DoubleDelegate();
+
+    public final static LongDelegate LONG = new LongDelegate();
+
+    private final static Long ZERO = new Long(0);
+
+    public final static Number add(final Object obj0, final Object obj1) {
+        if (obj0 == null && obj1 == null) {
+            return new Long(0);
+        }
+
+        final ELArithmetic delegate;
+        if (BIGDECIMAL.matches(obj0, obj1))
+            delegate = BIGDECIMAL;
+        else if (DOUBLE.matches(obj0, obj1))
+            delegate = DOUBLE;
+        else if (BIGINTEGER.matches(obj0, obj1))
+            delegate = BIGINTEGER;
+        else
+            delegate = LONG;
+
+        Number num0 = delegate.coerce(obj0);
+        Number num1 = delegate.coerce(obj1);
+
+        return delegate.add(num0, num1);
+    }
+
+    public final static Number mod(final Object obj0, final Object obj1) {
+        if (obj0 == null && obj1 == null) {
+            return new Long(0);
+        }
+
+        final ELArithmetic delegate;
+        if (BIGDECIMAL.matches(obj0, obj1))
+            delegate = BIGDECIMAL;
+        else if (DOUBLE.matches(obj0, obj1))
+            delegate = DOUBLE;
+        else if (BIGINTEGER.matches(obj0, obj1))
+            delegate = BIGINTEGER;
+        else
+            delegate = LONG;
+
+        Number num0 = delegate.coerce(obj0);
+        Number num1 = delegate.coerce(obj1);
+
+        return delegate.mod(num0, num1);
+    }
+
+    public final static Number subtract(final Object obj0, final Object obj1) {
+        if (obj0 == null && obj1 == null) {
+            return new Long(0);
+        }
+
+        final ELArithmetic delegate;
+        if (BIGDECIMAL.matches(obj0, obj1))
+            delegate = BIGDECIMAL;
+        else if (DOUBLE.matches(obj0, obj1))
+            delegate = DOUBLE;
+        else if (BIGINTEGER.matches(obj0, obj1))
+            delegate = BIGINTEGER;   
+        else
+            delegate = LONG;
+
+        Number num0 = delegate.coerce(obj0);
+        Number num1 = delegate.coerce(obj1);
+
+        return delegate.subtract(num0, num1);
+    }
+
+    public final static Number divide(final Object obj0, final Object obj1) {
+        if (obj0 == null && obj1 == null) {
+            return ZERO;
+        }
+
+        final ELArithmetic delegate;
+        if (BIGDECIMAL.matches(obj0, obj1))
+            delegate = BIGDECIMAL;
+        else if (BIGINTEGER.matches(obj0, obj1))
+            delegate = BIGDECIMAL;
+        else
+            delegate = DOUBLE;
+
+        Number num0 = delegate.coerce(obj0);
+        Number num1 = delegate.coerce(obj1);
+
+        return delegate.divide(num0, num1);
+    }
+
+    public final static Number multiply(final Object obj0, final Object obj1) {
+        if (obj0 == null && obj1 == null) {
+            return new Long(0);
+        }
+
+        final ELArithmetic delegate;
+        if (BIGDECIMAL.matches(obj0, obj1))
+            delegate = BIGDECIMAL;
+        else if (DOUBLE.matches(obj0, obj1))
+            delegate = DOUBLE;
+        else if (BIGINTEGER.matches(obj0, obj1))
+            delegate = BIGINTEGER;
+        else
+            delegate = LONG;
+
+        Number num0 = delegate.coerce(obj0);
+        Number num1 = delegate.coerce(obj1);
+
+        return delegate.multiply(num0, num1);
+    }
+
+    public final static boolean isNumber(final Object obj) {
+        return (obj != null && isNumberType(obj.getClass()));
+    }
+
+    public final static boolean isNumberType(final Class type) {
+        return type == (java.lang.Long.class) || type == Long.TYPE || type == (java.lang.Double.class) || type == Double.TYPE || type == (java.lang.Byte.class) || type == Byte.TYPE || type == (java.lang.Short.class) || type == Short.TYPE || type == (java.lang.Integer.class) || type == Integer.TYPE || type == (java.lang.Float.class) || type == Float.TYPE || type == (java.math.BigInteger.class) || type == (java.math.BigDecimal.class);
+    }
+
+    /**
+     * 
+     */
+    protected ELArithmetic() {
+        super();
+    }
+
+    protected abstract Number add(final Number num0, final Number num1);
+
+    protected abstract Number multiply(final Number num0, final Number num1);
+
+    protected abstract Number subtract(final Number num0, final Number num1);
+
+    protected abstract Number mod(final Number num0, final Number num1);
+
+    protected abstract Number coerce(final Number num);
+
+    protected final Number coerce(final Object obj) {
+        
+        if (isNumber(obj)) {
+            return coerce((Number) obj);
+        }
+        if (obj instanceof String) {
+            return coerce((String) obj);
+        }
+        if (obj == null || "".equals(obj)) {
+            return coerce(ZERO);
+        }
+
+        Class objType = obj.getClass();
+        if (Character.class.equals(objType) || Character.TYPE == objType) {
+            return coerce(new Short((short) ((Character) obj).charValue()));
+        }
+
+        throw new IllegalArgumentException(MessageFactory.get("el.convert", obj,
+                objType));
+    }
+
+    protected abstract Number coerce(final String str);
+
+    protected abstract Number divide(final Number num0, final Number num1);
+
+    protected abstract boolean matches(final Object obj0, final Object obj1);
+
+}
diff --git a/src/org/jdesktop/el/impl/lang/ELSupport.java b/src/org/jdesktop/el/impl/lang/ELSupport.java
new file mode 100644
index 0000000..eedeac8
--- /dev/null
+++ b/src/org/jdesktop/el/impl/lang/ELSupport.java
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el.impl.lang;
+
+import java.beans.PropertyEditor;
+import java.beans.PropertyEditorManager;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import org.jdesktop.el.ELException;
+import org.jdesktop.el.PropertyNotFoundException;
+
+import org.jdesktop.el.impl.util.MessageFactory;
+
+/**
+ * A helper class that implements the EL Specification
+ * 
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public class ELSupport {
+
+    private final static ELSupport REF = new ELSupport();
+
+    private final static Long ZERO = new Long(0L);
+
+    public final static void throwUnhandled(Object base, Object property)
+            throws ELException {
+        if (base == null) {
+            throw new PropertyNotFoundException(MessageFactory.get(
+                    "error.resolver.unhandled.null", property));
+        } else {
+            throw new PropertyNotFoundException(MessageFactory.get(
+                    "error.resolver.unhandled", base.getClass(), property));
+        }
+    }
+
+    /**
+     * @param obj0
+     * @param obj1
+     * @return
+     * @throws EvaluationException
+     */
+    public final static int compare(final Object obj0, final Object obj1)
+            throws ELException {
+        if (obj0 == obj1 || equals(obj0, obj1)) {
+            return 0;
+        }
+        if (isBigDecimalOp(obj0, obj1)) {
+            BigDecimal bd0 = (BigDecimal) coerceToNumber(obj0, BigDecimal.class);
+            BigDecimal bd1 = (BigDecimal) coerceToNumber(obj1, BigDecimal.class);
+            return bd0.compareTo(bd1);
+        }
+        if (isDoubleOp(obj0, obj1)) {
+            Double d0 = (Double) coerceToNumber(obj0, Double.class);
+            Double d1 = (Double) coerceToNumber(obj1, Double.class);
+            return d0.compareTo(d1);
+        }
+        if (isBigIntegerOp(obj0, obj1)) {
+            BigInteger bi0 = (BigInteger) coerceToNumber(obj0, BigInteger.class);
+            BigInteger bi1 = (BigInteger) coerceToNumber(obj1, BigInteger.class);
+            return bi0.compareTo(bi1);
+        }
+        if (isLongOp(obj0, obj1)) {
+            Long l0 = (Long) coerceToNumber(obj0, Long.class);
+            Long l1 = (Long) coerceToNumber(obj1, Long.class);
+            return l0.compareTo(l1);
+        }
+        if (obj0 instanceof String || obj1 instanceof String) {
+            return coerceToString(obj0).compareTo(coerceToString(obj1));
+        }
+        if (obj0 instanceof Comparable) {
+            return (obj1 != null) ? ((Comparable) obj0).compareTo(obj1) : 1;
+        }
+        if (obj1 instanceof Comparable) {
+            return (obj0 != null) ? -((Comparable) obj1).compareTo(obj0) : -1;
+        }
+        throw new ELException(MessageFactory.get("error.compare", obj0, obj1));
+    }
+
+    /**
+     * @param obj0
+     * @param obj1
+     * @return
+     * @throws EvaluationException
+     */
+    public final static boolean equals(final Object obj0, final Object obj1)
+            throws ELException {
+        if (obj0 == obj1) {
+            return true;
+        }
+        if (obj0 == null || obj1 == null) {
+            return false;
+        }
+        if (obj0 instanceof Boolean || obj1 instanceof Boolean) {
+            return coerceToBoolean(obj0).equals(coerceToBoolean(obj1));
+        }
+        if (obj0.getClass().isEnum()) {
+            return obj0.equals(coerceToEnum(obj1, obj0.getClass()));
+        }
+        if (obj1.getClass().isEnum()) {
+            return obj1.equals(coerceToEnum(obj0, obj1.getClass()));
+        }
+        if (obj0 instanceof String || obj1 instanceof String) {
+            return coerceToString(obj0).equals(coerceToString(obj1));
+        }
+        if (isBigDecimalOp(obj0, obj1)) {
+            BigDecimal bd0 = (BigDecimal) coerceToNumber(obj0, BigDecimal.class);
+            BigDecimal bd1 = (BigDecimal) coerceToNumber(obj1, BigDecimal.class);
+            return bd0.equals(bd1);
+        }
+        if (isDoubleOp(obj0, obj1)) {
+            Double d0 = (Double) coerceToNumber(obj0, Double.class);
+            Double d1 = (Double) coerceToNumber(obj1, Double.class);
+            return d0.equals(d1);
+        }
+        if (isBigIntegerOp(obj0, obj1)) {
+            BigInteger bi0 = (BigInteger) coerceToNumber(obj0, BigInteger.class);
+            BigInteger bi1 = (BigInteger) coerceToNumber(obj1, BigInteger.class);
+            return bi0.equals(bi1);
+        }
+        if (isLongOp(obj0, obj1)) {
+            Long l0 = (Long) coerceToNumber(obj0, Long.class);
+            Long l1 = (Long) coerceToNumber(obj1, Long.class);
+            return l0.equals(l1);
+        } else {
+            return obj0.equals(obj1);
+        }
+    }
+
+    /**
+     * @param obj
+     * @return
+     */
+    public final static Boolean coerceToBoolean(final Object obj)
+            throws IllegalArgumentException {
+        if (obj == null || "".equals(obj)) {
+            return Boolean.FALSE;
+        }
+        if (obj instanceof Boolean || obj.getClass() == Boolean.TYPE) {
+            return (Boolean) obj;
+        }
+        if (obj instanceof String) {
+            return Boolean.valueOf((String) obj);
+        }
+
+        throw new IllegalArgumentException(MessageFactory.get("error.convert",
+                obj, obj.getClass(), Boolean.class));
+    }
+
+    public final static Enum coerceToEnum(final Object obj, Class type)
+            throws IllegalArgumentException {
+        if (obj == null || "".equals(obj)) {
+            return null;
+        }
+        if (type.isInstance(obj)) {
+            return (Enum)obj;
+        }
+        if (obj instanceof String) {
+            return Enum.valueOf(type, (String) obj);
+        }
+
+        throw new IllegalArgumentException(MessageFactory.get("error.convert",
+                obj, obj.getClass(), type));
+    }
+
+    public final static Character coerceToCharacter(final Object obj)
+            throws IllegalArgumentException {
+        if (obj == null || "".equals(obj)) {
+            return new Character((char) 0);
+        }
+        if (obj instanceof String) {
+            return new Character(((String) obj).charAt(0));
+        }
+        if (ELArithmetic.isNumber(obj)) {
+            return new Character((char) ((Number) obj).shortValue());
+        }
+        Class objType = obj.getClass();
+        if (obj instanceof Character || objType == Character.TYPE) {
+            return (Character) obj;
+        }
+
+        throw new IllegalArgumentException(MessageFactory.get("error.convert",
+                obj, objType, Character.class));
+    }
+
+    public final static Number coerceToNumber(final Object obj) {
+        if (obj == null) {
+            return ZERO;
+        } else if (obj instanceof Number) {
+            return (Number) obj;
+        } else {
+            String str = coerceToString(obj);
+            if (isStringFloat(str)) {
+                return toFloat(str);
+            } else {
+                return toNumber(str);
+            }
+        }
+    }
+
+    protected final static Number coerceToNumber(final Number number,
+            final Class type) throws IllegalArgumentException {
+        if (Long.TYPE == type || Long.class.equals(type)) {
+            return new Long(number.longValue());
+        }
+        if (Double.TYPE == type || Double.class.equals(type)) {
+            return new Double(number.doubleValue());
+        }
+        if (Integer.TYPE == type || Integer.class.equals(type)) {
+            return new Integer(number.intValue());
+        }
+        if (BigInteger.class.equals(type)) {
+            if (number instanceof BigDecimal) {
+                return ((BigDecimal) number).toBigInteger();
+            }
+            return BigInteger.valueOf(number.longValue());
+        }
+        if (BigDecimal.class.equals(type)) {
+            if (number instanceof BigInteger) {
+                return new BigDecimal((BigInteger) number);
+            }
+//            return new BigDecimal(number.toString());
+            return new BigDecimal(number.doubleValue());
+        }
+        if (Byte.TYPE == type || Byte.class.equals(type)) {
+            return new Byte(number.byteValue());
+        }
+        if (Short.TYPE == type || Short.class.equals(type)) {
+            return new Short(number.shortValue());
+        }
+        if (Float.TYPE == type || Float.class.equals(type)) {
+            return new Float(number.floatValue());
+        }
+
+        throw new IllegalArgumentException(MessageFactory.get("error.convert",
+                number, number.getClass(), type));
+    }
+
+    public final static Number coerceToNumber(final Object obj, final Class type)
+            throws IllegalArgumentException {
+        if (obj == null || "".equals(obj)) {
+            return coerceToNumber(ZERO, type);
+        }
+        if (obj instanceof String) {
+            return coerceToNumber((String) obj, type);
+        }
+        if (ELArithmetic.isNumber(obj)) {
+            return coerceToNumber((Number) obj, type);
+        }
+
+        Class objType = obj.getClass();
+        if (Character.class.equals(objType) || Character.TYPE == objType) {
+            return coerceToNumber(new Short((short) ((Character) obj)
+                    .charValue()), type);
+        }
+
+        throw new IllegalArgumentException(MessageFactory.get("error.convert",
+                obj, objType, type));
+    }
+
+    protected final static Number coerceToNumber(final String val,
+            final Class type) throws IllegalArgumentException {
+        if (Long.TYPE == type || Long.class.equals(type)) {
+            return Long.valueOf(val);
+        }
+        if (Integer.TYPE == type || Integer.class.equals(type)) {
+            return Integer.valueOf(val);
+        }
+        if (Double.TYPE == type || Double.class.equals(type)) {
+            return Double.valueOf(val);
+        }
+        if (BigInteger.class.equals(type)) {
+            return new BigInteger(val);
+        }
+        if (BigDecimal.class.equals(type)) {
+            return new BigDecimal(val);
+        }
+        if (Byte.TYPE == type || Byte.class.equals(type)) {
+            return Byte.valueOf(val);
+        }
+        if (Short.TYPE == type || Short.class.equals(type)) {
+            return Short.valueOf(val);
+        }
+        if (Float.TYPE == type || Float.class.equals(type)) {
+            return Float.valueOf(val);
+        }
+
+        throw new IllegalArgumentException(MessageFactory.get("error.convert",
+                val, String.class, type));
+    }
+
+    /**
+     * @param obj
+     * @return
+     */
+    public final static String coerceToString(final Object obj) {
+        if (obj == null) {
+            return "";
+        } else if (obj instanceof String) {
+            return (String) obj;
+        } else if (obj instanceof Enum) {
+            return ((Enum) obj).name();
+        } else {
+            return obj.toString();
+        }
+    }
+
+    public final static Object coerceToType(final Object obj, final Class type)
+            throws IllegalArgumentException {
+        if (type == null || Object.class.equals(type)) {
+            return obj;
+        }
+        if (String.class.equals(type)) {
+            return coerceToString(obj);
+        }
+        if (ELArithmetic.isNumberType(type)) {
+            return coerceToNumber(obj, type);
+        }
+        if (Character.class.equals(type) || Character.TYPE == type) {
+            return coerceToCharacter(obj);
+        }
+        if (Boolean.class.equals(type) || Boolean.TYPE == type) {
+            return coerceToBoolean(obj);
+        }
+        if (type.isEnum()) {
+            return coerceToEnum(obj, type);
+        }
+        if (obj != null && type.isAssignableFrom(obj.getClass())) {
+            return obj;
+        }
+
+        // new to spec
+        if (obj == null)
+            return null;
+        if (obj instanceof String) {
+            if ("".equals(obj))
+                return null;
+            PropertyEditor editor = PropertyEditorManager.findEditor(type);
+            if (editor != null) {
+                editor.setAsText((String) obj);
+                return editor.getValue();
+            }
+        }
+        throw new IllegalArgumentException(MessageFactory.get("error.convert",
+                obj, obj.getClass(), type));
+    }
+
+    /**
+     * @param obj
+     * @return
+     */
+    public final static boolean containsNulls(final Object[] obj) {
+        for (int i = 0; i < obj.length; i++) {
+            if (obj[0] == null) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public final static boolean isBigDecimalOp(final Object obj0,
+            final Object obj1) {
+        return (obj0 instanceof BigDecimal || obj1 instanceof BigDecimal);
+    }
+
+    public final static boolean isBigIntegerOp(final Object obj0,
+            final Object obj1) {
+        return (obj0 instanceof BigInteger || obj1 instanceof BigInteger);
+    }
+
+    public final static boolean isDoubleOp(final Object obj0, final Object obj1) {
+        return (obj0 instanceof Double
+                || obj1 instanceof Double
+                || obj0 instanceof Float
+                || obj1 instanceof Float
+                || (obj0 != null && (Double.TYPE == obj0.getClass() || Float.TYPE == obj0
+                        .getClass())) || (obj1 != null && (Double.TYPE == obj1
+                .getClass() || Float.TYPE == obj1.getClass())));
+    }
+
+    public final static boolean isDoubleStringOp(final Object obj0,
+            final Object obj1) {
+        return (isDoubleOp(obj0, obj1)
+                || (obj0 instanceof String && isStringFloat((String) obj0)) || (obj1 instanceof String && isStringFloat((String) obj1)));
+    }
+
+    public final static boolean isLongOp(final Object obj0, final Object obj1) {
+        return (obj0 instanceof Long
+                || obj1 instanceof Long
+                || obj0 instanceof Integer
+                || obj1 instanceof Integer
+                || obj0 instanceof Character
+                || obj1 instanceof Character
+                || obj0 instanceof Short
+                || obj1 instanceof Short
+                || obj0 instanceof Byte
+                || obj1 instanceof Byte
+                || (obj0 != null && (Long.TYPE == obj0.getClass()
+                        || Integer.TYPE == obj0.getClass()
+                        || Character.TYPE == obj0.getClass()
+                        || Short.TYPE == obj0.getClass() || Byte.TYPE == obj0
+                        .getClass())) || (obj0 != null && (Long.TYPE == obj0
+                .getClass()
+                || Integer.TYPE == obj0.getClass()
+                || Character.TYPE == obj0.getClass()
+                || Short.TYPE == obj0.getClass() || Byte.TYPE == obj0
+                .getClass())));
+    }
+
+    public final static boolean isStringFloat(final String str) {
+        int len = str.length();
+        if (len > 1) {
+            char c = 0;
+            for (int i = 0; i < len; i++) {
+                switch (c = str.charAt(i)) {
+                case 'E':
+                    return true;
+                case 'e':
+                    return true;
+                case '.':
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public final static Number toFloat(final String value) {
+        try {
+            if (Double.parseDouble(value) > Double.MAX_VALUE) {
+                return new BigDecimal(value);
+            } else {
+                return new Double(value);
+            }
+        } catch (NumberFormatException e0) {
+            return new BigDecimal(value);
+        }
+    }
+
+    public final static Number toNumber(final String value) {
+        try {
+            return new Integer(Integer.parseInt(value));
+        } catch (NumberFormatException e0) {
+            try {
+                return new Long(Long.parseLong(value));
+            } catch (NumberFormatException e1) {
+                return new BigInteger(value);
+            }
+        }
+    }
+
+    /**
+     * 
+     */
+    public ELSupport() {
+        super();
+    }
+
+}
diff --git a/src/org/jdesktop/el/impl/lang/EvaluationContext.java b/src/org/jdesktop/el/impl/lang/EvaluationContext.java
new file mode 100644
index 0000000..2e637f3
--- /dev/null
+++ b/src/org/jdesktop/el/impl/lang/EvaluationContext.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el.impl.lang;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.ELResolver;
+import org.jdesktop.el.Expression;
+import org.jdesktop.el.FunctionMapper;
+import org.jdesktop.el.VariableMapper;
+
+public final class EvaluationContext extends ELContext {
+
+    private final ELContext elContext;
+
+    private final FunctionMapper fnMapper;
+
+    private final VariableMapper varMapper;
+
+    private final Expression expression;
+
+    private final Set<Expression.ResolvedProperty> currentIdentifierProperties;
+    private final Set<Expression.ResolvedProperty> resolvedProperties;
+
+    public EvaluationContext(ELContext elContext, FunctionMapper fnMapper,
+            VariableMapper varMapper, Expression expression) {
+        this(elContext, fnMapper, varMapper, expression, false);
+    }
+
+    public EvaluationContext(ELContext elContext, FunctionMapper fnMapper,
+            VariableMapper varMapper, Expression expression, boolean trackResolvedProperties) {
+        this.elContext = elContext;
+        this.fnMapper = fnMapper;
+        this.varMapper = varMapper;
+        this.expression = expression;
+        if (trackResolvedProperties) {
+            resolvedProperties = new LinkedHashSet<Expression.ResolvedProperty>(1);
+            currentIdentifierProperties = new LinkedHashSet<Expression.ResolvedProperty>(1);
+        } else {
+            resolvedProperties = null;
+            currentIdentifierProperties = null;
+        }
+    }
+    
+    public ELContext getELContext() {
+        return this.elContext;
+    }
+
+    public FunctionMapper getFunctionMapper() {
+        return this.fnMapper;
+    }
+
+    public VariableMapper getVariableMapper() {
+        return this.varMapper;
+    }
+
+    public Expression getExpression() {
+        return expression;
+    }
+    
+    public Object getContext(Class key) {
+        return this.elContext.getContext(key);
+    }
+
+    public ELResolver getELResolver() {
+        return this.elContext.getELResolver();
+    }
+
+    public boolean isPropertyResolved() {
+        return this.elContext.isPropertyResolved();
+    }
+
+    public void putContext(Class key, Object contextObject) {
+        this.elContext.putContext(key, contextObject);
+    }
+
+    public void setPropertyResolved(boolean resolved) {
+        this.elContext.setPropertyResolved(resolved);
+    }
+
+    public void clearResolvedProperties() {
+        if (resolvedProperties == null) {
+            return;
+        }
+
+        resolvedProperties.clear();
+    }
+
+    public void resolvedIdentifier(Object base, Object property) {
+        if (base == null || property == null || resolvedProperties == null) {
+            return;
+        }
+
+        resolvedProperties.addAll(currentIdentifierProperties);
+        currentIdentifierProperties.clear();
+        Expression.ResolvedProperty prop = new Expression.ResolvedProperty(base, property);
+        resolvedProperties.remove(prop);
+        currentIdentifierProperties.add(prop);
+    }
+
+    public void resolvedProperty(Object base, Object property) {
+        if (base == null || property == null || resolvedProperties == null) {
+            return;
+        }
+
+        Expression.ResolvedProperty prop = new Expression.ResolvedProperty(base, property);
+        resolvedProperties.remove(prop);
+        currentIdentifierProperties.add(prop);
+    }
+
+    public List<Expression.ResolvedProperty> getResolvedProperties() {
+        if (resolvedProperties == null) {
+            return null;
+        }
+
+        resolvedProperties.addAll(currentIdentifierProperties);
+        return new ArrayList<Expression.ResolvedProperty>(resolvedProperties);
+    }
+    
+}
diff --git a/src/org/jdesktop/el/impl/lang/ExpressionBuilder.java b/src/org/jdesktop/el/impl/lang/ExpressionBuilder.java
new file mode 100644
index 0000000..8c961b0
--- /dev/null
+++ b/src/org/jdesktop/el/impl/lang/ExpressionBuilder.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el.impl.lang;
+
+import java.io.StringReader;
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.ELException;
+import org.jdesktop.el.FunctionMapper;
+import org.jdesktop.el.MethodExpression;
+import org.jdesktop.el.ValueExpression;
+import org.jdesktop.el.VariableMapper;
+
+import org.jdesktop.el.impl.MethodExpressionImpl;
+import org.jdesktop.el.impl.MethodExpressionLiteral;
+import org.jdesktop.el.impl.ValueExpressionImpl;
+import org.jdesktop.el.impl.parser.AstCompositeExpression;
+import org.jdesktop.el.impl.parser.AstDeferredExpression;
+import org.jdesktop.el.impl.parser.AstDynamicExpression;
+import org.jdesktop.el.impl.parser.AstFunction;
+import org.jdesktop.el.impl.parser.AstIdentifier;
+import org.jdesktop.el.impl.parser.AstLiteralExpression;
+import org.jdesktop.el.impl.parser.AstValue;
+import org.jdesktop.el.impl.parser.ELParser;
+import org.jdesktop.el.impl.parser.Node;
+import org.jdesktop.el.impl.parser.NodeVisitor;
+import org.jdesktop.el.impl.parser.ParseException;
+import org.jdesktop.el.impl.util.MessageFactory;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class ExpressionBuilder implements NodeVisitor {
+
+    private static final int SIZE = 5000;
+    private static final Map cache = new ConcurrentHashMap(SIZE);
+    private static final Map cache2 = new ConcurrentHashMap(SIZE);
+
+    private FunctionMapper fnMapper;
+
+    private VariableMapper varMapper;
+
+    private String expression;
+
+    /**
+     * 
+     */
+    public ExpressionBuilder(String expression, ELContext ctx)
+            throws ELException {
+        this.expression = expression;
+
+        FunctionMapper ctxFn = ctx.getFunctionMapper();
+        VariableMapper ctxVar = ctx.getVariableMapper();
+
+        if (ctxFn != null) {
+            this.fnMapper = new FunctionMapperFactory(ctxFn);
+        }
+        if (ctxVar != null) {
+            this.varMapper = new VariableMapperFactory(ctxVar);
+        }
+    }
+
+    public final static Node createNode(String expr) throws ELException {
+        Node n = createNodeInternal(expr);
+        return n;
+    }
+
+    private final static Node createNodeInternal(String expr)
+            throws ELException {
+        if (expr == null) {
+            throw new ELException(MessageFactory.get("error.null"));
+        }
+
+        Node n = (Node) cache.get(expr);
+        if (n == null && (n = (Node) cache2.get(expr)) == null) {
+            try {
+                n = (new ELParser(new StringReader(expr)))
+                        .CompositeExpression();
+
+                // validate composite expression
+                if (n instanceof AstCompositeExpression) {
+                    int numChildren = n.jjtGetNumChildren();
+                    if (numChildren == 1) {
+                        n = n.jjtGetChild(0);
+                    } else {
+                        Class type = null;
+                        Node child = null;
+                        for (int i = 0; i < numChildren; i++) {
+                            child = n.jjtGetChild(i);
+                            if (child instanceof AstLiteralExpression)
+                                continue;
+                            if (type == null)
+                                type = child.getClass();
+                            else {
+                                if (!type.equals(child.getClass())) {
+                                    throw new ELException(MessageFactory.get(
+                                            "error.mixed", expr));
+                                }
+                            }
+                        }
+                    }
+                }
+                if (n instanceof AstDeferredExpression
+                        || n instanceof AstDynamicExpression) {
+                    n = n.jjtGetChild(0);
+                }
+                if (cache.size() > SIZE) {
+                    cache2.clear();
+                    cache2.putAll(cache);
+                    cache.clear();
+                }
+                cache.put(expr, n);
+            } catch (ParseException pe) {
+                throw new ELException("Error Parsing: " + expr, pe);
+            }
+        }
+        return n;
+    }
+
+    private void prepare(Node node) throws ELException {
+        node.accept(this);
+        if (this.fnMapper instanceof FunctionMapperFactory) {
+            this.fnMapper = ((FunctionMapperFactory) this.fnMapper).create();
+        }
+        if (this.varMapper instanceof VariableMapperFactory) {
+            this.varMapper = ((VariableMapperFactory) this.varMapper).create();
+        }
+    }
+
+    private Node build() throws ELException {
+        Node n = createNodeInternal(this.expression);
+        this.prepare(n);
+        if (n instanceof AstDeferredExpression
+                || n instanceof AstDynamicExpression) {
+            n = n.jjtGetChild(0);
+        }
+        return n;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see com.sun.el.parser.NodeVisitor#visit(com.sun.el.parser.Node)
+     */
+    public void visit(Node node) throws ELException {
+        if (node instanceof AstFunction) {
+
+            AstFunction funcNode = (AstFunction) node;
+
+            if (this.fnMapper == null) {
+                throw new ELException(MessageFactory.get("error.fnMapper.null"));
+            }
+            Method m = fnMapper.resolveFunction(funcNode.getPrefix(), funcNode
+                    .getLocalName());
+            if (m == null) {
+                throw new ELException(MessageFactory.get(
+                        "error.fnMapper.method", funcNode.getOutputName()));
+            }
+            int pcnt = m.getParameterTypes().length;
+            if (node.jjtGetNumChildren() != pcnt) {
+                throw new ELException(MessageFactory.get(
+                        "error.fnMapper.paramcount", funcNode.getOutputName(),
+                        "" + pcnt, "" + node.jjtGetNumChildren()));
+            }
+        } else if (node instanceof AstIdentifier && this.varMapper != null) {
+            String variable = ((AstIdentifier) node).getImage();
+
+            // simply capture it
+            this.varMapper.resolveVariable(variable);
+        }
+    }
+
+    public ValueExpression createValueExpression(Class expectedType)
+            throws ELException {
+        Node n = this.build();
+        return new ValueExpressionImpl(this.expression, n, this.fnMapper,
+                this.varMapper, expectedType);
+    }
+
+    public MethodExpression createMethodExpression(Class expectedReturnType,
+            Class[] expectedParamTypes) throws ELException {
+        Node n = this.build();
+        if (n instanceof AstValue || n instanceof AstIdentifier) {
+            return new MethodExpressionImpl(expression, n,
+                    this.fnMapper, this.varMapper, expectedReturnType,
+                    expectedParamTypes);
+        } else if (n instanceof AstLiteralExpression) {
+            return new MethodExpressionLiteral(expression, expectedReturnType,
+                    expectedParamTypes);
+        } else {
+            throw new ELException("Not a Valid Method Expression: "
+                    + expression);
+        }
+    }
+}
diff --git a/src/org/jdesktop/el/impl/lang/FunctionMapperFactory.java b/src/org/jdesktop/el/impl/lang/FunctionMapperFactory.java
new file mode 100644
index 0000000..721b270
--- /dev/null
+++ b/src/org/jdesktop/el/impl/lang/FunctionMapperFactory.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el.impl.lang;
+
+import java.lang.reflect.Method;
+
+import org.jdesktop.el.FunctionMapper;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public class FunctionMapperFactory extends FunctionMapper {
+
+    protected FunctionMapperImpl memento = null;
+    protected FunctionMapper target;
+    
+    public FunctionMapperFactory(FunctionMapper mapper) {
+        if (mapper == null) {
+            throw new NullPointerException("FunctionMapper target cannot be null");
+        }
+        this.target = mapper;
+    }
+   
+    
+    /* (non-Javadoc)
+     * @see javax.el.FunctionMapper#resolveFunction(java.lang.String, java.lang.String)
+     */
+    public Method resolveFunction(String prefix, String localName) {
+        if (this.memento == null) {
+            this.memento = new FunctionMapperImpl();
+        }
+        Method m = this.target.resolveFunction(prefix, localName);
+        if (m != null) {
+            this.memento.addFunction(prefix, localName, m);
+        }
+        return m;
+    }
+    
+    public FunctionMapper create() {
+        return this.memento;
+    }
+
+}
diff --git a/src/org/jdesktop/el/impl/lang/FunctionMapperImpl.java b/src/org/jdesktop/el/impl/lang/FunctionMapperImpl.java
new file mode 100644
index 0000000..9da9113
--- /dev/null
+++ b/src/org/jdesktop/el/impl/lang/FunctionMapperImpl.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el.impl.lang;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.jdesktop.el.FunctionMapper;
+
+import org.jdesktop.el.impl.util.ReflectionUtil;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public class FunctionMapperImpl extends FunctionMapper implements
+        Externalizable {
+
+    private static final long serialVersionUID = 1L;
+    
+    protected Map functions = null;
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see javax.el.FunctionMapper#resolveFunction(java.lang.String,
+     *      java.lang.String)
+     */
+    public Method resolveFunction(String prefix, String localName) {
+        if (this.functions != null) {
+            Function f = (Function) this.functions.get(prefix + ":" + localName);
+            return f.getMethod();
+        }
+        return null;
+    }
+
+    public void addFunction(String prefix, String localName, Method m) {
+        if (this.functions == null) {
+            this.functions = new HashMap();
+        }
+        Function f = new Function(prefix, localName, m);
+        synchronized (this) {
+            this.functions.put(prefix+":"+localName, f);
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.io.Externalizable#writeExternal(java.io.ObjectOutput)
+     */
+    public void writeExternal(ObjectOutput out) throws IOException {
+        out.writeObject(this.functions);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.io.Externalizable#readExternal(java.io.ObjectInput)
+     */
+    public void readExternal(ObjectInput in) throws IOException,
+            ClassNotFoundException {
+        this.functions = (Map) in.readObject();
+    }
+    
+    public static class Function implements Externalizable {
+    
+        protected transient Method m;
+        protected String owner;
+        protected String name;
+        protected String[] types;
+        protected String prefix;
+        protected String localName;
+    
+        /**
+         * 
+         */
+        public Function(String prefix, String localName, Method m) {
+            if (localName == null) {
+                throw new NullPointerException("LocalName cannot be null");
+            }
+            if (m == null) {
+                throw new NullPointerException("Method cannot be null");
+            }
+            this.prefix = prefix;
+            this.localName = localName;
+            this.m = m;
+        }
+        
+        public Function() {
+            // for serialization
+        }
+    
+        /*
+         * (non-Javadoc)
+         * 
+         * @see java.io.Externalizable#writeExternal(java.io.ObjectOutput)
+         */
+        public void writeExternal(ObjectOutput out) throws IOException {
+            
+            out.writeUTF((this.prefix != null) ? this.prefix : "");
+            out.writeUTF(this.localName);
+            
+            if (this.owner != null) {
+                out.writeUTF(this.owner);
+            } else {
+                out.writeUTF(this.m.getDeclaringClass().getName());
+            }
+            if (this.name != null) {
+                out.writeUTF(this.name);
+            } else {
+                out.writeUTF(this.m.getName());
+            }
+            if (this.types != null) {
+                out.writeObject(this.types);
+            } else {
+                out.writeObject(ReflectionUtil.toTypeNameArray(this.m.getParameterTypes()));
+            }
+        }
+    
+        /*
+         * (non-Javadoc)
+         * 
+         * @see java.io.Externalizable#readExternal(java.io.ObjectInput)
+         */
+        public void readExternal(ObjectInput in) throws IOException,
+                ClassNotFoundException {
+            
+            this.prefix = in.readUTF();
+            if ("".equals(this.prefix)) this.prefix = null;
+            this.localName = in.readUTF();
+            this.owner = in.readUTF();
+            this.name = in.readUTF();
+            this.types = (String[]) in.readObject();
+        }
+    
+        public Method getMethod() {
+            if (this.m == null) {
+                try {
+                    Class t = Class.forName(this.owner);
+                    Class[] p = ReflectionUtil.toTypeArray(this.types);
+                    this.m = t.getMethod(this.name, p);
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+            return this.m;
+        }
+        
+        public boolean matches(String prefix, String localName) {
+            if (this.prefix != null) {
+                if (prefix == null) return false;
+                if (!this.prefix.equals(prefix)) return false;
+            }
+            return this.localName.equals(localName);
+        }
+    
+        /* (non-Javadoc)
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        public boolean equals(Object obj) {
+            if (obj == this) {
+                return true;
+            }
+            if (obj instanceof Function) {
+                Function of = (Function) obj;
+                return (of.prefix.equals(prefix) &&
+                        of.localName.equals(localName));
+            }
+            return false;
+        }
+        
+        /* (non-Javadoc)
+         * @see java.lang.Object#hashCode()
+         */
+        public int hashCode() {
+            return (this.prefix + this.localName).hashCode();
+        }
+    }
+
+}
diff --git a/src/org/jdesktop/el/impl/lang/VariableMapperFactory.java b/src/org/jdesktop/el/impl/lang/VariableMapperFactory.java
new file mode 100644
index 0000000..80876dd
--- /dev/null
+++ b/src/org/jdesktop/el/impl/lang/VariableMapperFactory.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el.impl.lang;
+
+import org.jdesktop.el.ValueExpression;
+import org.jdesktop.el.VariableMapper;
+
+public class VariableMapperFactory extends VariableMapper {
+
+    private final VariableMapper target;
+    private VariableMapper momento;
+    
+    public VariableMapperFactory(VariableMapper target) {
+        if (target == null) {
+            throw new NullPointerException("Target VariableMapper cannot be null");
+        }
+        this.target = target;
+    }
+    
+    public VariableMapper create() {
+        return this.momento;
+    }
+
+    public ValueExpression resolveVariable(String variable) {
+        ValueExpression expr = this.target.resolveVariable(variable);
+        if (expr != null) {
+            if (this.momento == null) {
+                this.momento = new VariableMapperImpl();
+            }
+            this.momento.setVariable(variable, expr);
+        }
+        return expr;
+    }
+
+    public ValueExpression setVariable(String variable, ValueExpression expression) {
+        throw new UnsupportedOperationException("Cannot Set Variables on Factory");
+    }
+}
diff --git a/src/org/jdesktop/el/impl/lang/VariableMapperImpl.java b/src/org/jdesktop/el/impl/lang/VariableMapperImpl.java
new file mode 100644
index 0000000..372eadb
--- /dev/null
+++ b/src/org/jdesktop/el/impl/lang/VariableMapperImpl.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el.impl.lang;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.jdesktop.el.ValueExpression;
+import org.jdesktop.el.VariableMapper;
+
+public class VariableMapperImpl extends VariableMapper implements Externalizable {
+
+    private static final long serialVersionUID = 1L;
+    
+    private Map vars = new HashMap();
+    
+    public VariableMapperImpl() {
+        super();
+    }
+
+    public ValueExpression resolveVariable(String variable) {
+        return (ValueExpression) this.vars.get(variable);
+    }
+
+    public ValueExpression setVariable(String variable,
+            ValueExpression expression) {
+        return (ValueExpression) this.vars.put(variable, expression);
+    }
+
+    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+        this.vars = (Map) in.readObject();
+    }
+
+    public void writeExternal(ObjectOutput out) throws IOException {
+        out.writeObject(this.vars);
+    }
+}
diff --git a/src/org/jdesktop/el/impl/lang/package.html b/src/org/jdesktop/el/impl/lang/package.html
new file mode 100644
index 0000000..6ae8488
--- /dev/null
+++ b/src/org/jdesktop/el/impl/lang/package.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<!--
+ Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ subject to license terms.
+-->
+
+<html>
+    <head>
+        <title></title>
+    </head>
+    <body>
+        <p>
+            Customized version of EL for Beans Binding; not for general use.
+            It is expected that we'll sync up with an official version of EL
+            in the future.
+        </p>
+    </body>
+</html>
diff --git a/src/org/jdesktop/el/impl/package.html b/src/org/jdesktop/el/impl/package.html
new file mode 100644
index 0000000..6ae8488
--- /dev/null
+++ b/src/org/jdesktop/el/impl/package.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<!--
+ Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ subject to license terms.
+-->
+
+<html>
+    <head>
+        <title></title>
+    </head>
+    <body>
+        <p>
+            Customized version of EL for Beans Binding; not for general use.
+            It is expected that we'll sync up with an official version of EL
+            in the future.
+        </p>
+    </body>
+</html>
diff --git a/src/org/jdesktop/el/impl/parser/ArithmeticNode.java b/src/org/jdesktop/el/impl/parser/ArithmeticNode.java
new file mode 100644
index 0000000..a6aa7d1
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/ArithmeticNode.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public class ArithmeticNode extends SimpleNode {
+
+    /**
+     * @param i
+     */
+    public ArithmeticNode(int i) {
+        super(i);
+    }
+
+    public Class getType(EvaluationContext ctx)
+            throws ELException {
+        return Number.class;
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstAnd.java b/src/org/jdesktop/el/impl/parser/AstAnd.java
new file mode 100644
index 0000000..dd93aac
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstAnd.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstAnd.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstAnd extends BooleanNode {
+    public AstAnd(int id) {
+        super(id);
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj = children[0].getValue(ctx);
+        if (obj == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        }
+        Boolean b = coerceToBoolean(obj);
+        if (!b.booleanValue()) {
+            return b;
+        }
+        obj = children[1].getValue(ctx);
+        if (obj == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        }
+        b = coerceToBoolean(obj);
+        return b;
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstBracketSuffix.java b/src/org/jdesktop/el/impl/parser/AstBracketSuffix.java
new file mode 100644
index 0000000..17aa076
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstBracketSuffix.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstBracketSuffix.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstBracketSuffix extends SimpleNode {
+    public AstBracketSuffix(int id) {
+        super(id);
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        return this.children[0].getValue(ctx);
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstChoice.java b/src/org/jdesktop/el/impl/parser/AstChoice.java
new file mode 100644
index 0000000..4785d75
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstChoice.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstChoice.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstChoice extends SimpleNode {
+    public AstChoice(int id) {
+        super(id);
+    }
+
+    public Class getType(EvaluationContext ctx)
+            throws ELException {
+        Object val = this.getValue(ctx);
+        return (val != null) ? val.getClass() : null;
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj0 = this.children[0].getValue(ctx);
+        if (obj0 == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        }
+        Boolean b0 = coerceToBoolean(obj0);
+        return this.children[((b0.booleanValue() ? 1 : 2))].getValue(ctx);
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstCompositeExpression.java b/src/org/jdesktop/el/impl/parser/AstCompositeExpression.java
new file mode 100644
index 0000000..778f1da
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstCompositeExpression.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstCompositeExpression.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstCompositeExpression extends SimpleNode {
+
+    public AstCompositeExpression(int id) {
+        super(id);
+    }
+
+    public Class getType(EvaluationContext ctx)
+            throws ELException {
+        return String.class;
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        StringBuffer sb = new StringBuffer(16);
+        Object obj = null;
+        if (this.children != null) {
+            for (int i = 0; i < this.children.length; i++) {
+                obj = this.children[i].getValue(ctx);
+                if (obj == ELContext.UNRESOLVABLE_RESULT) {
+                    return ELContext.UNRESOLVABLE_RESULT;
+                }
+                if (obj != null) {
+                    sb.append(obj);
+                }
+            }
+        }
+        return sb.toString();
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstDeferredExpression.java b/src/org/jdesktop/el/impl/parser/AstDeferredExpression.java
new file mode 100644
index 0000000..67bee70
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstDeferredExpression.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstDeferredExpression.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstDeferredExpression extends SimpleNode {
+    public AstDeferredExpression(int id) {
+        super(id);
+    }
+
+    public Class getType(EvaluationContext ctx)
+            throws ELException {
+        return this.children[0].getType(ctx);
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        return this.children[0].getValue(ctx);
+    }
+
+    public boolean isReadOnly(EvaluationContext ctx)
+            throws ELException {
+        return this.children[0].isReadOnly(ctx);
+    }
+
+    public void setValue(EvaluationContext ctx, Object value)
+            throws ELException {
+        this.children[0].setValue(ctx, value);
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstDiv.java b/src/org/jdesktop/el/impl/parser/AstDiv.java
new file mode 100644
index 0000000..4a98739
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstDiv.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstDiv.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.ELArithmetic;
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstDiv extends ArithmeticNode {
+    public AstDiv(int id) {
+        super(id);
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj0 = this.children[0].getValue(ctx);
+        if (obj0 == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        }
+        Object obj1 = this.children[1].getValue(ctx);
+        if (obj1 == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        }
+        return ELArithmetic.divide(obj0, obj1);
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstDotSuffix.java b/src/org/jdesktop/el/impl/parser/AstDotSuffix.java
new file mode 100644
index 0000000..b36a2c1
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstDotSuffix.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstDotSuffix.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstDotSuffix extends SimpleNode {
+    public AstDotSuffix(int id) {
+        super(id);
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        return this.image;
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstDynamicExpression.java b/src/org/jdesktop/el/impl/parser/AstDynamicExpression.java
new file mode 100644
index 0000000..1684baf
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstDynamicExpression.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstDynamicExpression.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstDynamicExpression extends SimpleNode {
+    public AstDynamicExpression(int id) {
+        super(id);
+    }
+
+    public Class getType(EvaluationContext ctx)
+            throws ELException {
+        return this.children[0].getType(ctx);
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        return this.children[0].getValue(ctx);
+    }
+
+    public boolean isReadOnly(EvaluationContext ctx)
+            throws ELException {
+        return this.children[0].isReadOnly(ctx);
+    }
+
+    public void setValue(EvaluationContext ctx, Object value)
+            throws ELException {
+        this.children[0].setValue(ctx, value);
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstEmpty.java b/src/org/jdesktop/el/impl/parser/AstEmpty.java
new file mode 100644
index 0000000..217dbad
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstEmpty.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstEmpty.java */
+
+package org.jdesktop.el.impl.parser;
+
+import java.util.Collection;
+import java.util.Map;
+import org.jdesktop.el.ELContext;
+
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstEmpty extends SimpleNode {
+    public AstEmpty(int id) {
+        super(id);
+    }
+
+    public Class getType(EvaluationContext ctx)
+            throws ELException {
+        return Boolean.class;
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj = this.children[0].getValue(ctx);
+        if (obj == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        } else if (obj == null) {
+            return Boolean.TRUE;
+        } else if (obj instanceof String) {
+            return Boolean.valueOf(((String) obj).length() == 0);
+        } else if (obj instanceof Object[]) {
+            return Boolean.valueOf(((Object[]) obj).length == 0);
+        } else if (obj instanceof Collection) {
+            return Boolean.valueOf(((Collection) obj).isEmpty());
+        } else if (obj instanceof Map) {
+            return Boolean.valueOf(((Map) obj).isEmpty());
+        }
+        return Boolean.FALSE;
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstEqual.java b/src/org/jdesktop/el/impl/parser/AstEqual.java
new file mode 100644
index 0000000..d7c82c2
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstEqual.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstEqual.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstEqual extends BooleanNode {
+    public AstEqual(int id) {
+        super(id);
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj0 = this.children[0].getValue(ctx);
+        if (obj0 == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        }
+        Object obj1 = this.children[1].getValue(ctx);
+        if (obj1 == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        }
+        return Boolean.valueOf(equals(obj0, obj1));
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstFalse.java b/src/org/jdesktop/el/impl/parser/AstFalse.java
new file mode 100644
index 0000000..e235a85
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstFalse.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstFalse.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstFalse extends BooleanNode {
+    public AstFalse(int id) {
+        super(id);
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        return Boolean.FALSE;
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstFloatingPoint.java b/src/org/jdesktop/el/impl/parser/AstFloatingPoint.java
new file mode 100644
index 0000000..a01da16
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstFloatingPoint.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstFloatingPoint.java */
+
+package org.jdesktop.el.impl.parser;
+
+import java.math.BigDecimal;
+
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstFloatingPoint extends SimpleNode {
+    public AstFloatingPoint(int id) {
+        super(id);
+    }
+
+    private Number number;
+
+    public Number getFloatingPoint() {
+        if (this.number == null) {
+            try {
+                this.number = new Double(this.image);
+            } catch (ArithmeticException e0) {
+                this.number = new BigDecimal(this.image);
+            }
+        }
+        return this.number;
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        return this.getFloatingPoint();
+    }
+
+    public Class getType(EvaluationContext ctx)
+            throws ELException {
+        return this.getFloatingPoint().getClass();
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstFunction.java b/src/org/jdesktop/el/impl/parser/AstFunction.java
new file mode 100644
index 0000000..a27332b
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstFunction.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstFunction.java */
+
+package org.jdesktop.el.impl.parser;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.ELException;
+import org.jdesktop.el.FunctionMapper;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+import org.jdesktop.el.impl.util.MessageFactory;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstFunction extends SimpleNode {
+
+    protected String localName = "";
+
+    protected String prefix = "";
+
+    public AstFunction(int id) {
+        super(id);
+    }
+
+    public String getLocalName() {
+        return localName;
+    }
+
+    public String getOutputName() {
+        if (this.prefix == null) {
+            return this.localName;
+        } else {
+            return this.prefix + ":" + this.localName;
+        }
+    }
+
+    public String getPrefix() {
+        return prefix;
+    }
+
+    public Class getType(EvaluationContext ctx)
+            throws ELException {
+        
+        FunctionMapper fnMapper = ctx.getFunctionMapper();
+        
+        // quickly validate again for this request
+        if (fnMapper == null) {
+            throw new ELException(MessageFactory.get("error.fnMapper.null"));
+        }
+        Method m = fnMapper.resolveFunction(this.prefix, this.localName);
+        if (m == null) {
+            throw new ELException(MessageFactory.get("error.fnMapper.method",
+                    this.getOutputName()));
+        }
+        return m.getReturnType();
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        
+        FunctionMapper fnMapper = ctx.getFunctionMapper();
+        
+        // quickly validate again for this request
+        if (fnMapper == null) {
+            throw new ELException(MessageFactory.get("error.fnMapper.null"));
+        }
+        Method m = fnMapper.resolveFunction(this.prefix, this.localName);
+        if (m == null) {
+            throw new ELException(MessageFactory.get("error.fnMapper.method",
+                    this.getOutputName()));
+        }
+
+        Class[] paramTypes = m.getParameterTypes();
+        Object[] params = null;
+        Object result = null;
+        int numParams = this.jjtGetNumChildren();
+        if (numParams > 0) {
+            params = new Object[numParams];
+            try {
+                for (int i = 0; i < numParams; i++) {
+                    params[i] = this.children[i].getValue(ctx);
+                    if (params[i] == ELContext.UNRESOLVABLE_RESULT) {
+                        return ELContext.UNRESOLVABLE_RESULT;
+                    }
+                    params[i] = coerceToType(params[i], paramTypes[i]);
+                }
+            } catch (ELException ele) {
+                throw new ELException(MessageFactory.get("error.function", this
+                        .getOutputName()), ele);
+            }
+        }
+        try {
+            result = m.invoke(null, params);
+        } catch (IllegalAccessException iae) {
+            throw new ELException(MessageFactory.get("error.function", this
+                    .getOutputName()), iae);
+        } catch (InvocationTargetException ite) {
+            throw new ELException(MessageFactory.get("error.function", this
+                    .getOutputName()), ite.getCause());
+        }
+        return result;
+    }
+
+    public void setLocalName(String localName) {
+        this.localName = localName;
+    }
+
+    public void setPrefix(String prefix) {
+        this.prefix = prefix;
+    }
+    
+    
+    public String toString()
+    {
+        return ELParserTreeConstants.jjtNodeName[id] + "[" + this.getOutputName() + "]";
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstGreaterThan.java b/src/org/jdesktop/el/impl/parser/AstGreaterThan.java
new file mode 100644
index 0000000..19b1777
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstGreaterThan.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstGreaterThan.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstGreaterThan extends BooleanNode {
+    public AstGreaterThan(int id) {
+        super(id);
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj0 = this.children[0].getValue(ctx);
+        if (obj0 == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        } else if (obj0 == null) {
+            return Boolean.FALSE;
+        }
+        Object obj1 = this.children[1].getValue(ctx);
+        if (obj1 == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        } else if (obj1 == null) {
+            return Boolean.FALSE;
+        }
+        return (compare(obj0, obj1) > 0) ? Boolean.TRUE : Boolean.FALSE;
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstGreaterThanEqual.java b/src/org/jdesktop/el/impl/parser/AstGreaterThanEqual.java
new file mode 100644
index 0000000..47f13a5
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstGreaterThanEqual.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstGreaterThanEqual.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstGreaterThanEqual extends BooleanNode {
+    public AstGreaterThanEqual(int id) {
+        super(id);
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj0 = this.children[0].getValue(ctx);
+        if (obj0 == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        }
+        Object obj1 = this.children[1].getValue(ctx);
+        if (obj1 == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        }
+        if (obj0 == obj1) {
+            return Boolean.TRUE;
+        }
+        if (obj0 == null || obj1 == null) {
+            return Boolean.FALSE;
+        }
+        return (compare(obj0, obj1) >= 0) ? Boolean.TRUE : Boolean.FALSE;
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstIdentifier.java b/src/org/jdesktop/el/impl/parser/AstIdentifier.java
new file mode 100644
index 0000000..bb3495d
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstIdentifier.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstIdentifier.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.ELException;
+import org.jdesktop.el.Expression;
+import org.jdesktop.el.MethodExpression;
+import org.jdesktop.el.MethodInfo;
+import org.jdesktop.el.MethodNotFoundException;
+import org.jdesktop.el.ValueExpression;
+import org.jdesktop.el.VariableMapper;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstIdentifier extends SimpleNode {
+    public AstIdentifier(int id) {
+        super(id);
+    }
+
+    public Class getType(EvaluationContext ctx) throws ELException {
+        VariableMapper varMapper = ctx.getVariableMapper();
+        if (varMapper != null) {
+            ValueExpression expr = varMapper.resolveVariable(this.image);
+            if (expr != null) {
+                return expr.getType(ctx.getELContext());
+            }
+        }
+        ctx.setPropertyResolved(false);
+        return ctx.getELResolver().getType(ctx, getSource(ctx), this.image);
+    }
+
+    public Object getValue(EvaluationContext ctx) throws ELException {
+        VariableMapper varMapper = ctx.getVariableMapper();
+        if (varMapper != null) {
+            ValueExpression expr = varMapper.resolveVariable(this.image);
+            if (expr != null) {
+                return expr.getValue(ctx.getELContext());
+            }
+        }
+        ctx.setPropertyResolved(false);
+        Object source = getSource(ctx);
+        Object retVal = ctx.getELResolver().getValue(ctx, source, this.image);
+        if (retVal != ELContext.UNRESOLVABLE_RESULT) {
+            ctx.resolvedIdentifier(source, this.image);
+        }
+        return retVal;
+    }
+
+    public boolean isReadOnly(EvaluationContext ctx) throws ELException {
+        VariableMapper varMapper = ctx.getVariableMapper();
+        if (varMapper != null) {
+            ValueExpression expr = varMapper.resolveVariable(this.image);
+            if (expr != null) {
+                return expr.isReadOnly(ctx.getELContext());
+            }
+        }
+        ctx.setPropertyResolved(false);
+        return ctx.getELResolver().isReadOnly(ctx, getSource(ctx), this.image);
+    }
+
+    public void setValue(EvaluationContext ctx, Object value)
+            throws ELException {
+        VariableMapper varMapper = ctx.getVariableMapper();
+        if (varMapper != null) {
+            ValueExpression expr = varMapper.resolveVariable(this.image);
+            if (expr != null) {
+                expr.setValue(ctx.getELContext(), value);
+                return;
+            }
+        }
+        ctx.setPropertyResolved(false);
+        ctx.getELResolver().setValue(ctx, getSource(ctx), this.image, value);
+    }
+
+    private final Object invokeTarget(EvaluationContext ctx, Object target,
+            Object[] paramValues) throws ELException {
+        if (target instanceof MethodExpression) {
+            MethodExpression me = (MethodExpression) target;
+            return me.invoke(ctx.getELContext(), paramValues);
+        } else if (target == null) {
+            throw new MethodNotFoundException("Identity '" + this.image
+                    + "' was null and was unable to invoke");
+        } else {
+            throw new ELException(
+                    "Identity '"
+                            + this.image
+                            + "' does not reference a MethodExpression instance, returned type: "
+                            + target.getClass().getName());
+        }
+    }
+
+    public Object invoke(EvaluationContext ctx, Class[] paramTypes,
+            Object[] paramValues) throws ELException {
+        return this.getMethodExpression(ctx).invoke(ctx.getELContext(), paramValues);
+    }
+    
+
+    public MethodInfo getMethodInfo(EvaluationContext ctx, Class[] paramTypes)
+            throws ELException {
+        return this.getMethodExpression(ctx).getMethodInfo(ctx.getELContext());
+    }
+
+    private final MethodExpression getMethodExpression(EvaluationContext ctx)
+            throws ELException {
+        Object obj = null;
+
+        // case A: ValueExpression exists, getValue which must
+        // be a MethodExpression
+        VariableMapper varMapper = ctx.getVariableMapper();
+        ValueExpression ve = null;
+        if (varMapper != null) {
+            ve = varMapper.resolveVariable(this.image);
+            if (ve != null) {
+                obj = ve.getValue(ctx);
+            }
+        }
+
+        // case B: evaluate the identity against the ELResolver, again, must be
+        // a MethodExpression to be able to invoke
+        if (ve == null) {
+            ctx.setPropertyResolved(false);
+            obj = ctx.getELResolver().getValue(ctx, null, this.image);
+        }
+
+        // finally provide helpful hints
+        if (obj instanceof MethodExpression) {
+            return (MethodExpression) obj;
+        } else if (obj == null) {
+            throw new MethodNotFoundException("Identity '" + this.image
+                    + "' was null and was unable to invoke");
+        } else {
+            throw new ELException(
+                    "Identity '"
+                            + this.image
+                            + "' does not reference a MethodExpression instance, returned type: "
+                            + obj.getClass().getName());
+        }
+    }
+
+    private Object getSource(EvaluationContext ctx) {
+        Expression expression = ctx.getExpression();
+        if (expression instanceof ValueExpression) {
+            return ((ValueExpression)expression).getSource();
+        }
+        return null;
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstInteger.java b/src/org/jdesktop/el/impl/parser/AstInteger.java
new file mode 100644
index 0000000..30290fe
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstInteger.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstInteger.java */
+
+package org.jdesktop.el.impl.parser;
+
+import java.math.BigInteger;
+
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstInteger extends SimpleNode {
+    public AstInteger(int id) {
+        super(id);
+    }
+
+    private Number number;
+
+    protected Number getInteger() {
+        if (this.number == null) {
+            try {
+                this.number = new Long(this.image);
+            } catch (ArithmeticException e1) {
+                this.number = new BigInteger(this.image);
+            }
+        }
+        return number;
+    }
+
+    public Class getType(EvaluationContext ctx)
+            throws ELException {
+        return this.getInteger().getClass();
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        return this.getInteger();
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstLessThan.java b/src/org/jdesktop/el/impl/parser/AstLessThan.java
new file mode 100644
index 0000000..9e4f8e6
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstLessThan.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstLessThan.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstLessThan extends BooleanNode {
+    public AstLessThan(int id) {
+        super(id);
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj0 = this.children[0].getValue(ctx);
+        if (obj0 == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        } else if (obj0 == null) {
+            return Boolean.FALSE;
+        }
+        Object obj1 = this.children[1].getValue(ctx);
+        if (obj1 == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        } else if (obj1 == null) {
+            return Boolean.FALSE;
+        }
+        return (compare(obj0, obj1) < 0) ? Boolean.TRUE : Boolean.FALSE;
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstLessThanEqual.java b/src/org/jdesktop/el/impl/parser/AstLessThanEqual.java
new file mode 100644
index 0000000..fb4bf90
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstLessThanEqual.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstLessThanEqual.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstLessThanEqual extends BooleanNode {
+    public AstLessThanEqual(int id) {
+        super(id);
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj0 = this.children[0].getValue(ctx);
+        if (obj0 == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        }
+        Object obj1 = this.children[1].getValue(ctx);
+        if (obj1 == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        }
+        if (obj0 == obj1) {
+            return Boolean.TRUE;
+        }
+        if (obj0 == null || obj1 == null) {
+            return Boolean.FALSE;
+        }
+        return (compare(obj0, obj1) <= 0) ? Boolean.TRUE : Boolean.FALSE;
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstLiteralExpression.java b/src/org/jdesktop/el/impl/parser/AstLiteralExpression.java
new file mode 100644
index 0000000..779427b
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstLiteralExpression.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstLiteralExpression.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstLiteralExpression extends SimpleNode {
+    public AstLiteralExpression(int id) {
+        super(id);
+    }
+
+    public Class getType(EvaluationContext ctx) throws ELException {
+        return String.class;
+    }
+
+    public Object getValue(EvaluationContext ctx) throws ELException {
+        return this.image;
+    }
+
+    public void setImage(String image) {
+        if (image.indexOf('\\') == -1) {
+            this.image = image;
+            return;
+        }
+        int size = image.length();
+        StringBuffer buf = new StringBuffer(size);
+        for (int i = 0; i < size; i++) {
+            char c = image.charAt(i);
+            if (c == '\\' && i + 1 < size) {
+                char c1 = image.charAt(i + 1);
+                if (c1 == '\\' || c1 == '"' || c1 == '\'' || c1 == '#'
+                        || c1 == '$') {
+                    c = c1;
+                    i++;
+                }
+            }
+            buf.append(c);
+        }
+        this.image = buf.toString();
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstMinus.java b/src/org/jdesktop/el/impl/parser/AstMinus.java
new file mode 100644
index 0000000..4b1b18a
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstMinus.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstMinus.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.ELArithmetic;
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstMinus extends ArithmeticNode {
+    public AstMinus(int id) {
+        super(id);
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj0 = this.children[0].getValue(ctx);
+        if (obj0 == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        }
+        Object obj1 = this.children[1].getValue(ctx);
+        if (obj1 == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        }
+        return ELArithmetic.subtract(obj0, obj1);
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstMod.java b/src/org/jdesktop/el/impl/parser/AstMod.java
new file mode 100644
index 0000000..fb04c77
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstMod.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstMod.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.ELArithmetic;
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstMod extends ArithmeticNode {
+    public AstMod(int id) {
+        super(id);
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj0 = this.children[0].getValue(ctx);
+        if (obj0 == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        }
+        Object obj1 = this.children[1].getValue(ctx);
+        if (obj1 == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        }
+        return ELArithmetic.mod(obj0, obj1);
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstMult.java b/src/org/jdesktop/el/impl/parser/AstMult.java
new file mode 100644
index 0000000..2cc5015
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstMult.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstMult.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.ELArithmetic;
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstMult extends ArithmeticNode {
+    public AstMult(int id) {
+        super(id);
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj0 = this.children[0].getValue(ctx);
+        if (obj0 == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        }
+        Object obj1 = this.children[1].getValue(ctx);
+        if (obj1 == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        }
+        return ELArithmetic.multiply(obj0, obj1);
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstNegative.java b/src/org/jdesktop/el/impl/parser/AstNegative.java
new file mode 100644
index 0000000..38d4f7e
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstNegative.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstNegative.java */
+
+package org.jdesktop.el.impl.parser;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import org.jdesktop.el.ELContext;
+
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstNegative extends SimpleNode {
+    public AstNegative(int id) {
+        super(id);
+    }
+
+    public Class getType(EvaluationContext ctx)
+            throws ELException {
+        return Number.class;
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj = this.children[0].getValue(ctx);
+
+        if (obj == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        }
+        if (obj == null) {
+            return new Long(0);
+        }
+        if (obj instanceof BigDecimal) {
+            return ((BigDecimal) obj).negate();
+        }
+        if (obj instanceof BigInteger) {
+            return ((BigInteger) obj).negate();
+        }
+        if (obj instanceof String) {
+            if (isStringFloat((String) obj)) {
+                return new Double(-Double.parseDouble((String) obj));
+            }
+            return new Long(-Long.parseLong((String) obj));
+        }
+        Class type = obj.getClass();
+        if (obj instanceof Long || Long.TYPE == type) {
+            return new Long(-((Long) obj).longValue());
+        }
+        if (obj instanceof Double || Double.TYPE == type) {
+            return new Double(-((Double) obj).doubleValue());
+        }
+        if (obj instanceof Integer || Integer.TYPE == type) {
+            return new Integer(-((Integer) obj).intValue());
+        }
+        if (obj instanceof Float || Float.TYPE == type) {
+            return new Float(-((Float) obj).floatValue());
+        }
+        if (obj instanceof Short || Short.TYPE == type) {
+            return new Short((short) -((Short) obj).shortValue());
+        }
+        if (obj instanceof Byte || Byte.TYPE == type) {
+            return new Byte((byte) -((Byte) obj).byteValue());
+        }
+        Long num = (Long) coerceToNumber(obj, Long.class);
+        return new Long(-num.longValue());
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstNot.java b/src/org/jdesktop/el/impl/parser/AstNot.java
new file mode 100644
index 0000000..542700b
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstNot.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstNot.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstNot extends SimpleNode {
+    public AstNot(int id) {
+        super(id);
+    }
+
+    public Class getType(EvaluationContext ctx)
+            throws ELException {
+        return Boolean.class;
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj = this.children[0].getValue(ctx);
+        if (obj == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        }
+        Boolean b = coerceToBoolean(obj);
+        return Boolean.valueOf(!b.booleanValue());
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstNotEqual.java b/src/org/jdesktop/el/impl/parser/AstNotEqual.java
new file mode 100644
index 0000000..20be136
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstNotEqual.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstNotEqual.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstNotEqual extends BooleanNode {
+    public AstNotEqual(int id) {
+        super(id);
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj0 = this.children[0].getValue(ctx);
+        if (obj0 == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        }
+        Object obj1 = this.children[1].getValue(ctx);
+        if (obj1 == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        }
+        return Boolean.valueOf(!equals(obj0, obj1));
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstNull.java b/src/org/jdesktop/el/impl/parser/AstNull.java
new file mode 100644
index 0000000..998cd20
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstNull.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstNull.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstNull extends SimpleNode {
+    public AstNull(int id) {
+        super(id);
+    }
+
+    public Class getType(EvaluationContext ctx)
+            throws ELException {
+        return null;
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        return null;
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstOr.java b/src/org/jdesktop/el/impl/parser/AstOr.java
new file mode 100644
index 0000000..27ed189
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstOr.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstOr.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstOr extends BooleanNode {
+    public AstOr(int id) {
+        super(id);
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj = this.children[0].getValue(ctx);
+        if (obj == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        }
+        Boolean b = coerceToBoolean(obj);
+        if (b.booleanValue()) {
+            return b;
+        }
+        obj = this.children[1].getValue(ctx);
+        if (obj == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        }
+        b = coerceToBoolean(obj);
+        return b;
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstPlus.java b/src/org/jdesktop/el/impl/parser/AstPlus.java
new file mode 100644
index 0000000..f91bf6a
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstPlus.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstPlus.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.ELArithmetic;
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstPlus extends ArithmeticNode {
+    public AstPlus(int id) {
+        super(id);
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        Object obj0 = this.children[0].getValue(ctx);
+        if (obj0 == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        }
+        Object obj1 = this.children[1].getValue(ctx);
+        if (obj1 == ELContext.UNRESOLVABLE_RESULT) {
+            return ELContext.UNRESOLVABLE_RESULT;
+        }
+        return ELArithmetic.add(obj0, obj1);
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstString.java b/src/org/jdesktop/el/impl/parser/AstString.java
new file mode 100644
index 0000000..0714abd
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstString.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstString.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstString extends SimpleNode {
+    public AstString(int id) {
+        super(id);
+    }
+
+    private String string;
+
+    public String getString() {
+        if (this.string == null) {
+            this.string = this.image.substring(1, this.image.length() - 1);
+        }
+        return this.string;
+    }
+
+    public Class getType(EvaluationContext ctx)
+            throws ELException {
+        return String.class;
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        return this.getString();
+    }
+
+    public void setImage(String image) {
+        if (image.indexOf('\\') == -1) {
+            this.image = image;
+            return;
+        }
+        int size = image.length();
+        StringBuffer buf = new StringBuffer(size);
+        for (int i = 0; i < size; i++) {
+            char c = image.charAt(i);
+            if (c == '\\' && i + 1 < size) {
+                char c1 = image.charAt(i + 1);
+                if (c1 == '\\' || c1 == '"' || c1 == '\'' || c1 == '#'
+                        || c1 == '$') {
+                    c = c1;
+                    i++;
+                }
+            }
+            buf.append(c);
+        }
+        this.image = buf.toString();
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstTrue.java b/src/org/jdesktop/el/impl/parser/AstTrue.java
new file mode 100644
index 0000000..4178929
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstTrue.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstTrue.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstTrue extends BooleanNode {
+    public AstTrue(int id) {
+        super(id);
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        return Boolean.TRUE;
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/AstValue.java b/src/org/jdesktop/el/impl/parser/AstValue.java
new file mode 100644
index 0000000..8f18e36
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/AstValue.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. AstValue.java */
+
+package org.jdesktop.el.impl.parser;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.jdesktop.el.ELContext;
+import org.jdesktop.el.ELException;
+import org.jdesktop.el.ELResolver;
+import org.jdesktop.el.MethodInfo;
+import org.jdesktop.el.PropertyNotFoundException;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+import org.jdesktop.el.impl.util.MessageFactory;
+import org.jdesktop.el.impl.util.ReflectionUtil;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class AstValue extends SimpleNode {
+
+    protected static class Target {
+        protected Object base;
+
+        protected Object property;
+    }
+
+    public AstValue(int id) {
+        super(id);
+    }
+
+    public Class getType(EvaluationContext ctx) throws ELException {
+        Target t = getTarget(ctx);
+        ctx.setPropertyResolved(false);
+        return ctx.getELResolver().getType(ctx, t.base, t.property);
+    }
+
+    private final Target getTarget(EvaluationContext ctx) throws ELException {
+        // evaluate expr-a to value-a
+        Object base = this.children[0].getValue(ctx);
+
+        // if our base is null (we know there are more properites to evaluate)
+        if (base == null || base == ELContext.UNRESOLVABLE_RESULT) {
+            throw new PropertyNotFoundException(MessageFactory.get(
+                    "error.unreachable.base", this.children[0].getImage()));
+        }
+
+        // set up our start/end
+        Object property = null;
+        int propCount = this.jjtGetNumChildren() - 1;
+        int i = 1;
+
+        // evaluate any properties before our target
+        ELResolver resolver = ctx.getELResolver();
+        if (propCount > 1) {
+            while (base != null && base != ELContext.UNRESOLVABLE_RESULT && i < propCount) {
+                property = this.children[i].getValue(ctx);
+                ctx.setPropertyResolved(false);
+                base = resolver.getValue(ctx, base, property);
+                i++;
+            }
+            // if we are in this block, we have more properties to resolve,
+            // but our base was null
+            if (base == ELContext.UNRESOLVABLE_RESULT || base == null || property == null) {
+                throw new PropertyNotFoundException(MessageFactory.get(
+                        "error.unreachable.property", property));
+            }
+        }
+
+        property = this.children[i].getValue(ctx);
+
+        if (property == null) {
+            throw new PropertyNotFoundException(MessageFactory.get(
+                    "error.unreachable.property", this.children[i]));
+        }
+
+        Target t = new Target();
+        t.base = base;
+        t.property = property;
+        return t;
+    }
+
+    public Object getValue(EvaluationContext ctx) throws ELException {
+        Object base = this.children[0].getValue(ctx);
+        int propCount = this.jjtGetNumChildren();
+        if (base == ELContext.UNRESOLVABLE_RESULT || (base == null && propCount > 1)) {
+            ctx.clearResolvedProperties();
+            return ELContext.UNRESOLVABLE_RESULT;
+        }
+        int i = 1;
+        Object property = null;
+        ELResolver resolver = ctx.getELResolver();
+        while (base != null && i < propCount) {
+            property = this.children[i].getValue(ctx);
+            if (property == null) {
+                return null;
+            } else {
+                ctx.setPropertyResolved(false);
+                Object origBase = base;
+                base = resolver.getValue(ctx, base, property);
+                if (base == ELContext.UNRESOLVABLE_RESULT) {
+                    ctx.clearResolvedProperties();
+                    return base;
+                } else {
+                    ctx.resolvedProperty(origBase, property);
+                }
+            }
+            i++;
+        }
+        if (base == null && i < propCount) {
+            ctx.clearResolvedProperties();
+            return ELContext.UNRESOLVABLE_RESULT;
+        }
+        return base;
+    }
+
+    public boolean isReadOnly(EvaluationContext ctx) throws ELException {
+        Target t = getTarget(ctx);
+        ctx.setPropertyResolved(false);
+        return ctx.getELResolver().isReadOnly(ctx, t.base, t.property);
+    }
+
+    public void setValue(EvaluationContext ctx, Object value)
+            throws ELException {
+        Target t = getTarget(ctx);
+        ctx.setPropertyResolved(false);
+        ctx.getELResolver().setValue(ctx, t.base, t.property, value);
+    }
+
+    public MethodInfo getMethodInfo(EvaluationContext ctx, Class[] paramTypes)
+            throws ELException {
+        Target t = getTarget(ctx);
+        Method m = ReflectionUtil.getMethod(t.base, t.property, paramTypes);
+        return new MethodInfo(m.getName(), m.getReturnType(), m
+                .getParameterTypes());
+    }
+
+    public Object invoke(EvaluationContext ctx, Class[] paramTypes,
+            Object[] paramValues) throws ELException {
+        Target t = getTarget(ctx);
+        Method m = ReflectionUtil.getMethod(t.base, t.property, paramTypes);
+        Object result = null;
+        try {
+            result = m.invoke(t.base, (Object[]) paramValues);
+        } catch (IllegalAccessException iae) {
+            throw new ELException(iae);
+        } catch (InvocationTargetException ite) {
+            throw new ELException(ite.getCause());
+        }
+        return result;
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/BooleanNode.java b/src/org/jdesktop/el/impl/parser/BooleanNode.java
new file mode 100644
index 0000000..8c84d57
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/BooleanNode.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELException;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public class BooleanNode extends SimpleNode {
+    /**
+     * @param i
+     */
+    public BooleanNode(int i) {
+        super(i);
+    }
+    public Class getType(EvaluationContext ctx)
+            throws ELException {
+        return Boolean.class;
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/ELParser.java b/src/org/jdesktop/el/impl/parser/ELParser.java
new file mode 100644
index 0000000..4868ddf
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/ELParser.java
@@ -0,0 +1,1676 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree&JavaCC: Do not edit this line. ELParser.java */
+package org.jdesktop.el.impl.parser;
+import java.io.StringReader;
+import org.jdesktop.el.ELException;
+public class ELParser/*@bgen(jjtree)*/implements ELParserTreeConstants, ELParserConstants {/*@bgen(jjtree)*/
+  protected JJTELParserState jjtree = new JJTELParserState();public static Node parse(String ref) throws ELException
+    {
+        try {
+                return (new ELParser(new StringReader(ref))).CompositeExpression();
+        } catch (ParseException pe) {
+                throw new ELException(pe.getMessage());
+        }
+    }
+
+/*
+ * CompositeExpression
+ * Allow most flexible parsing, restrict by examining
+ * type of returned node
+ */
+  final public AstCompositeExpression CompositeExpression() throws ParseException {
+                                                                     /*@bgen(jjtree) CompositeExpression */
+  AstCompositeExpression jjtn000 = new AstCompositeExpression(JJTCOMPOSITEEXPRESSION);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+    try {
+      label_1:
+      while (true) {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case LITERAL_EXPRESSION:
+        case START_DYNAMIC_EXPRESSION:
+        case START_DEFERRED_EXPRESSION:
+          ;
+          break;
+        default:
+          jj_la1[0] = jj_gen;
+          break label_1;
+        }
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case START_DEFERRED_EXPRESSION:
+          DeferredExpression();
+          break;
+        case START_DYNAMIC_EXPRESSION:
+          DynamicExpression();
+          break;
+        case LITERAL_EXPRESSION:
+          LiteralExpression();
+          break;
+        default:
+          jj_la1[1] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+      }
+      jj_consume_token(0);
+                                                                                    jjtree.closeNodeScope(jjtn000, true);
+                                                                                    jjtc000 = false;
+                                                                                    {if (true) return jjtn000;}
+    } catch (Throwable jjte000) {
+          if (jjtc000) {
+            jjtree.clearNodeScope(jjtn000);
+            jjtc000 = false;
+          } else {
+            jjtree.popNode();
+          }
+          if (jjte000 instanceof RuntimeException) {
+            {if (true) throw (RuntimeException)jjte000;}
+          }
+          if (jjte000 instanceof ParseException) {
+            {if (true) throw (ParseException)jjte000;}
+          }
+          {if (true) throw (Error)jjte000;}
+    } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+          }
+    }
+    throw new Error("Missing return statement in function");
+  }
+
+/*
+ * LiteralExpression
+ * Non-EL Expression blocks
+ */
+  final public void LiteralExpression() throws ParseException {
+                                               /*@bgen(jjtree) LiteralExpression */
+                                                AstLiteralExpression jjtn000 = new AstLiteralExpression(JJTLITERALEXPRESSION);
+                                                boolean jjtc000 = true;
+                                                jjtree.openNodeScope(jjtn000);Token t = null;
+    try {
+      t = jj_consume_token(LITERAL_EXPRESSION);
+                                 jjtree.closeNodeScope(jjtn000, true);
+                                 jjtc000 = false;
+                                 jjtn000.setImage(t.image);
+    } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+          }
+    }
+  }
+
+/*
+ * DeferredExpression
+ * #{..} Expressions
+ */
+  final public void DeferredExpression() throws ParseException {
+                                                 /*@bgen(jjtree) DeferredExpression */
+  AstDeferredExpression jjtn000 = new AstDeferredExpression(JJTDEFERREDEXPRESSION);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+    try {
+      jj_consume_token(START_DEFERRED_EXPRESSION);
+      Expression();
+      jj_consume_token(END_EXPRESSION);
+    } catch (Throwable jjte000) {
+          if (jjtc000) {
+            jjtree.clearNodeScope(jjtn000);
+            jjtc000 = false;
+          } else {
+            jjtree.popNode();
+          }
+          if (jjte000 instanceof RuntimeException) {
+            {if (true) throw (RuntimeException)jjte000;}
+          }
+          if (jjte000 instanceof ParseException) {
+            {if (true) throw (ParseException)jjte000;}
+          }
+          {if (true) throw (Error)jjte000;}
+    } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+          }
+    }
+  }
+
+/*
+ * DynamicExpression
+ * ${..} Expressions
+ */
+  final public void DynamicExpression() throws ParseException {
+                                               /*@bgen(jjtree) DynamicExpression */
+  AstDynamicExpression jjtn000 = new AstDynamicExpression(JJTDYNAMICEXPRESSION);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+    try {
+      jj_consume_token(START_DYNAMIC_EXPRESSION);
+      Expression();
+      jj_consume_token(END_EXPRESSION);
+    } catch (Throwable jjte000) {
+          if (jjtc000) {
+            jjtree.clearNodeScope(jjtn000);
+            jjtc000 = false;
+          } else {
+            jjtree.popNode();
+          }
+          if (jjte000 instanceof RuntimeException) {
+            {if (true) throw (RuntimeException)jjte000;}
+          }
+          if (jjte000 instanceof ParseException) {
+            {if (true) throw (ParseException)jjte000;}
+          }
+          {if (true) throw (Error)jjte000;}
+    } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+          }
+    }
+  }
+
+/*
+ * Expression
+ * EL Expression Language Root, goes to Choice
+ */
+  final public void Expression() throws ParseException {
+    Choice();
+  }
+
+/*
+ * Choice
+ * For Choice markup a ? b : c, then Or
+ */
+  final public void Choice() throws ParseException {
+    Or();
+    label_2:
+    while (true) {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case QUESTIONMARK:
+        ;
+        break;
+      default:
+        jj_la1[2] = jj_gen;
+        break label_2;
+      }
+      jj_consume_token(QUESTIONMARK);
+      Or();
+      jj_consume_token(COLON);
+                                            AstChoice jjtn001 = new AstChoice(JJTCHOICE);
+                                            boolean jjtc001 = true;
+                                            jjtree.openNodeScope(jjtn001);
+      try {
+        Choice();
+      } catch (Throwable jjte001) {
+                                            if (jjtc001) {
+                                              jjtree.clearNodeScope(jjtn001);
+                                              jjtc001 = false;
+                                            } else {
+                                              jjtree.popNode();
+                                            }
+                                            if (jjte001 instanceof RuntimeException) {
+                                              {if (true) throw (RuntimeException)jjte001;}
+                                            }
+                                            if (jjte001 instanceof ParseException) {
+                                              {if (true) throw (ParseException)jjte001;}
+                                            }
+                                            {if (true) throw (Error)jjte001;}
+      } finally {
+                                            if (jjtc001) {
+                                              jjtree.closeNodeScope(jjtn001,  3);
+                                            }
+      }
+    }
+  }
+
+/*
+ * Or
+ * For 'or' '||', then And
+ */
+  final public void Or() throws ParseException {
+    And();
+    label_3:
+    while (true) {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case OR0:
+      case OR1:
+        ;
+        break;
+      default:
+        jj_la1[3] = jj_gen;
+        break label_3;
+      }
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case OR0:
+        jj_consume_token(OR0);
+        break;
+      case OR1:
+        jj_consume_token(OR1);
+        break;
+      default:
+        jj_la1[4] = jj_gen;
+        jj_consume_token(-1);
+        throw new ParseException();
+      }
+                               AstOr jjtn001 = new AstOr(JJTOR);
+                               boolean jjtc001 = true;
+                               jjtree.openNodeScope(jjtn001);
+      try {
+        And();
+      } catch (Throwable jjte001) {
+                               if (jjtc001) {
+                                 jjtree.clearNodeScope(jjtn001);
+                                 jjtc001 = false;
+                               } else {
+                                 jjtree.popNode();
+                               }
+                               if (jjte001 instanceof RuntimeException) {
+                                 {if (true) throw (RuntimeException)jjte001;}
+                               }
+                               if (jjte001 instanceof ParseException) {
+                                 {if (true) throw (ParseException)jjte001;}
+                               }
+                               {if (true) throw (Error)jjte001;}
+      } finally {
+                               if (jjtc001) {
+                                 jjtree.closeNodeScope(jjtn001,  2);
+                               }
+      }
+    }
+  }
+
+/*
+ * And
+ * For 'and' '&&', then Equality
+ */
+  final public void And() throws ParseException {
+    Equality();
+    label_4:
+    while (true) {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case AND0:
+      case AND1:
+        ;
+        break;
+      default:
+        jj_la1[5] = jj_gen;
+        break label_4;
+      }
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case AND0:
+        jj_consume_token(AND0);
+        break;
+      case AND1:
+        jj_consume_token(AND1);
+        break;
+      default:
+        jj_la1[6] = jj_gen;
+        jj_consume_token(-1);
+        throw new ParseException();
+      }
+                                      AstAnd jjtn001 = new AstAnd(JJTAND);
+                                      boolean jjtc001 = true;
+                                      jjtree.openNodeScope(jjtn001);
+      try {
+        Equality();
+      } catch (Throwable jjte001) {
+                                      if (jjtc001) {
+                                        jjtree.clearNodeScope(jjtn001);
+                                        jjtc001 = false;
+                                      } else {
+                                        jjtree.popNode();
+                                      }
+                                      if (jjte001 instanceof RuntimeException) {
+                                        {if (true) throw (RuntimeException)jjte001;}
+                                      }
+                                      if (jjte001 instanceof ParseException) {
+                                        {if (true) throw (ParseException)jjte001;}
+                                      }
+                                      {if (true) throw (Error)jjte001;}
+      } finally {
+                                      if (jjtc001) {
+                                        jjtree.closeNodeScope(jjtn001,  2);
+                                      }
+      }
+    }
+  }
+
+/*
+ * Equality
+ * For '==' 'eq' '!=' 'ne', then Compare
+ */
+  final public void Equality() throws ParseException {
+    Compare();
+    label_5:
+    while (true) {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case EQ0:
+      case EQ1:
+      case NE0:
+      case NE1:
+        ;
+        break;
+      default:
+        jj_la1[7] = jj_gen;
+        break label_5;
+      }
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case EQ0:
+      case EQ1:
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case EQ0:
+          jj_consume_token(EQ0);
+          break;
+        case EQ1:
+          jj_consume_token(EQ1);
+          break;
+        default:
+          jj_la1[8] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+                                 AstEqual jjtn001 = new AstEqual(JJTEQUAL);
+                                 boolean jjtc001 = true;
+                                 jjtree.openNodeScope(jjtn001);
+        try {
+          Compare();
+        } catch (Throwable jjte001) {
+                                 if (jjtc001) {
+                                   jjtree.clearNodeScope(jjtn001);
+                                   jjtc001 = false;
+                                 } else {
+                                   jjtree.popNode();
+                                 }
+                                 if (jjte001 instanceof RuntimeException) {
+                                   {if (true) throw (RuntimeException)jjte001;}
+                                 }
+                                 if (jjte001 instanceof ParseException) {
+                                   {if (true) throw (ParseException)jjte001;}
+                                 }
+                                 {if (true) throw (Error)jjte001;}
+        } finally {
+                                 if (jjtc001) {
+                                   jjtree.closeNodeScope(jjtn001,  2);
+                                 }
+        }
+        break;
+      case NE0:
+      case NE1:
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case NE0:
+          jj_consume_token(NE0);
+          break;
+        case NE1:
+          jj_consume_token(NE1);
+          break;
+        default:
+          jj_la1[9] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+                                 AstNotEqual jjtn002 = new AstNotEqual(JJTNOTEQUAL);
+                                 boolean jjtc002 = true;
+                                 jjtree.openNodeScope(jjtn002);
+        try {
+          Compare();
+        } catch (Throwable jjte002) {
+                                 if (jjtc002) {
+                                   jjtree.clearNodeScope(jjtn002);
+                                   jjtc002 = false;
+                                 } else {
+                                   jjtree.popNode();
+                                 }
+                                 if (jjte002 instanceof RuntimeException) {
+                                   {if (true) throw (RuntimeException)jjte002;}
+                                 }
+                                 if (jjte002 instanceof ParseException) {
+                                   {if (true) throw (ParseException)jjte002;}
+                                 }
+                                 {if (true) throw (Error)jjte002;}
+        } finally {
+                                 if (jjtc002) {
+                                   jjtree.closeNodeScope(jjtn002,  2);
+                                 }
+        }
+        break;
+      default:
+        jj_la1[10] = jj_gen;
+        jj_consume_token(-1);
+        throw new ParseException();
+      }
+    }
+  }
+
+/*
+ * Compare
+ * For a bunch of them, then Math
+ */
+  final public void Compare() throws ParseException {
+    Math();
+    label_6:
+    while (true) {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case GT0:
+      case GT1:
+      case LT0:
+      case LT1:
+      case GE0:
+      case GE1:
+      case LE0:
+      case LE1:
+        ;
+        break;
+      default:
+        jj_la1[11] = jj_gen;
+        break label_6;
+      }
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case LT0:
+      case LT1:
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case LT0:
+          jj_consume_token(LT0);
+          break;
+        case LT1:
+          jj_consume_token(LT1);
+          break;
+        default:
+          jj_la1[12] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+                                 AstLessThan jjtn001 = new AstLessThan(JJTLESSTHAN);
+                                 boolean jjtc001 = true;
+                                 jjtree.openNodeScope(jjtn001);
+        try {
+          Math();
+        } catch (Throwable jjte001) {
+                                 if (jjtc001) {
+                                   jjtree.clearNodeScope(jjtn001);
+                                   jjtc001 = false;
+                                 } else {
+                                   jjtree.popNode();
+                                 }
+                                 if (jjte001 instanceof RuntimeException) {
+                                   {if (true) throw (RuntimeException)jjte001;}
+                                 }
+                                 if (jjte001 instanceof ParseException) {
+                                   {if (true) throw (ParseException)jjte001;}
+                                 }
+                                 {if (true) throw (Error)jjte001;}
+        } finally {
+                                 if (jjtc001) {
+                                   jjtree.closeNodeScope(jjtn001,  2);
+                                 }
+        }
+        break;
+      case GT0:
+      case GT1:
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case GT0:
+          jj_consume_token(GT0);
+          break;
+        case GT1:
+          jj_consume_token(GT1);
+          break;
+        default:
+          jj_la1[13] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+                                 AstGreaterThan jjtn002 = new AstGreaterThan(JJTGREATERTHAN);
+                                 boolean jjtc002 = true;
+                                 jjtree.openNodeScope(jjtn002);
+        try {
+          Math();
+        } catch (Throwable jjte002) {
+                                 if (jjtc002) {
+                                   jjtree.clearNodeScope(jjtn002);
+                                   jjtc002 = false;
+                                 } else {
+                                   jjtree.popNode();
+                                 }
+                                 if (jjte002 instanceof RuntimeException) {
+                                   {if (true) throw (RuntimeException)jjte002;}
+                                 }
+                                 if (jjte002 instanceof ParseException) {
+                                   {if (true) throw (ParseException)jjte002;}
+                                 }
+                                 {if (true) throw (Error)jjte002;}
+        } finally {
+                                 if (jjtc002) {
+                                   jjtree.closeNodeScope(jjtn002,  2);
+                                 }
+        }
+        break;
+      case LE0:
+      case LE1:
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case LE0:
+          jj_consume_token(LE0);
+          break;
+        case LE1:
+          jj_consume_token(LE1);
+          break;
+        default:
+          jj_la1[14] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+                                 AstLessThanEqual jjtn003 = new AstLessThanEqual(JJTLESSTHANEQUAL);
+                                 boolean jjtc003 = true;
+                                 jjtree.openNodeScope(jjtn003);
+        try {
+          Math();
+        } catch (Throwable jjte003) {
+                                 if (jjtc003) {
+                                   jjtree.clearNodeScope(jjtn003);
+                                   jjtc003 = false;
+                                 } else {
+                                   jjtree.popNode();
+                                 }
+                                 if (jjte003 instanceof RuntimeException) {
+                                   {if (true) throw (RuntimeException)jjte003;}
+                                 }
+                                 if (jjte003 instanceof ParseException) {
+                                   {if (true) throw (ParseException)jjte003;}
+                                 }
+                                 {if (true) throw (Error)jjte003;}
+        } finally {
+                                 if (jjtc003) {
+                                   jjtree.closeNodeScope(jjtn003,  2);
+                                 }
+        }
+        break;
+      case GE0:
+      case GE1:
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case GE0:
+          jj_consume_token(GE0);
+          break;
+        case GE1:
+          jj_consume_token(GE1);
+          break;
+        default:
+          jj_la1[15] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+                                 AstGreaterThanEqual jjtn004 = new AstGreaterThanEqual(JJTGREATERTHANEQUAL);
+                                 boolean jjtc004 = true;
+                                 jjtree.openNodeScope(jjtn004);
+        try {
+          Math();
+        } catch (Throwable jjte004) {
+                                 if (jjtc004) {
+                                   jjtree.clearNodeScope(jjtn004);
+                                   jjtc004 = false;
+                                 } else {
+                                   jjtree.popNode();
+                                 }
+                                 if (jjte004 instanceof RuntimeException) {
+                                   {if (true) throw (RuntimeException)jjte004;}
+                                 }
+                                 if (jjte004 instanceof ParseException) {
+                                   {if (true) throw (ParseException)jjte004;}
+                                 }
+                                 {if (true) throw (Error)jjte004;}
+        } finally {
+                                 if (jjtc004) {
+                                   jjtree.closeNodeScope(jjtn004,  2);
+                                 }
+        }
+        break;
+      default:
+        jj_la1[16] = jj_gen;
+        jj_consume_token(-1);
+        throw new ParseException();
+      }
+    }
+  }
+
+/*
+ * Math
+ * For '+' '-', then Multiplication
+ */
+  final public void Math() throws ParseException {
+    Multiplication();
+    label_7:
+    while (true) {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case PLUS:
+      case MINUS:
+        ;
+        break;
+      default:
+        jj_la1[17] = jj_gen;
+        break label_7;
+      }
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case PLUS:
+        jj_consume_token(PLUS);
+                          AstPlus jjtn001 = new AstPlus(JJTPLUS);
+                          boolean jjtc001 = true;
+                          jjtree.openNodeScope(jjtn001);
+        try {
+          Multiplication();
+        } catch (Throwable jjte001) {
+                          if (jjtc001) {
+                            jjtree.clearNodeScope(jjtn001);
+                            jjtc001 = false;
+                          } else {
+                            jjtree.popNode();
+                          }
+                          if (jjte001 instanceof RuntimeException) {
+                            {if (true) throw (RuntimeException)jjte001;}
+                          }
+                          if (jjte001 instanceof ParseException) {
+                            {if (true) throw (ParseException)jjte001;}
+                          }
+                          {if (true) throw (Error)jjte001;}
+        } finally {
+                          if (jjtc001) {
+                            jjtree.closeNodeScope(jjtn001,  2);
+                          }
+        }
+        break;
+      case MINUS:
+        jj_consume_token(MINUS);
+                           AstMinus jjtn002 = new AstMinus(JJTMINUS);
+                           boolean jjtc002 = true;
+                           jjtree.openNodeScope(jjtn002);
+        try {
+          Multiplication();
+        } catch (Throwable jjte002) {
+                           if (jjtc002) {
+                             jjtree.clearNodeScope(jjtn002);
+                             jjtc002 = false;
+                           } else {
+                             jjtree.popNode();
+                           }
+                           if (jjte002 instanceof RuntimeException) {
+                             {if (true) throw (RuntimeException)jjte002;}
+                           }
+                           if (jjte002 instanceof ParseException) {
+                             {if (true) throw (ParseException)jjte002;}
+                           }
+                           {if (true) throw (Error)jjte002;}
+        } finally {
+                           if (jjtc002) {
+                             jjtree.closeNodeScope(jjtn002,  2);
+                           }
+        }
+        break;
+      default:
+        jj_la1[18] = jj_gen;
+        jj_consume_token(-1);
+        throw new ParseException();
+      }
+    }
+  }
+
+/*
+ * Multiplication
+ * For a bunch of them, then Unary
+ */
+  final public void Multiplication() throws ParseException {
+    Unary();
+    label_8:
+    while (true) {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case MULT:
+      case DIV0:
+      case DIV1:
+      case MOD0:
+      case MOD1:
+        ;
+        break;
+      default:
+        jj_la1[19] = jj_gen;
+        break label_8;
+      }
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case MULT:
+        jj_consume_token(MULT);
+                          AstMult jjtn001 = new AstMult(JJTMULT);
+                          boolean jjtc001 = true;
+                          jjtree.openNodeScope(jjtn001);
+        try {
+          Unary();
+        } catch (Throwable jjte001) {
+                          if (jjtc001) {
+                            jjtree.clearNodeScope(jjtn001);
+                            jjtc001 = false;
+                          } else {
+                            jjtree.popNode();
+                          }
+                          if (jjte001 instanceof RuntimeException) {
+                            {if (true) throw (RuntimeException)jjte001;}
+                          }
+                          if (jjte001 instanceof ParseException) {
+                            {if (true) throw (ParseException)jjte001;}
+                          }
+                          {if (true) throw (Error)jjte001;}
+        } finally {
+                          if (jjtc001) {
+                            jjtree.closeNodeScope(jjtn001,  2);
+                          }
+        }
+        break;
+      case DIV0:
+      case DIV1:
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case DIV0:
+          jj_consume_token(DIV0);
+          break;
+        case DIV1:
+          jj_consume_token(DIV1);
+          break;
+        default:
+          jj_la1[20] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+                                   AstDiv jjtn002 = new AstDiv(JJTDIV);
+                                   boolean jjtc002 = true;
+                                   jjtree.openNodeScope(jjtn002);
+        try {
+          Unary();
+        } catch (Throwable jjte002) {
+                                   if (jjtc002) {
+                                     jjtree.clearNodeScope(jjtn002);
+                                     jjtc002 = false;
+                                   } else {
+                                     jjtree.popNode();
+                                   }
+                                   if (jjte002 instanceof RuntimeException) {
+                                     {if (true) throw (RuntimeException)jjte002;}
+                                   }
+                                   if (jjte002 instanceof ParseException) {
+                                     {if (true) throw (ParseException)jjte002;}
+                                   }
+                                   {if (true) throw (Error)jjte002;}
+        } finally {
+                                   if (jjtc002) {
+                                     jjtree.closeNodeScope(jjtn002,  2);
+                                   }
+        }
+        break;
+      case MOD0:
+      case MOD1:
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case MOD0:
+          jj_consume_token(MOD0);
+          break;
+        case MOD1:
+          jj_consume_token(MOD1);
+          break;
+        default:
+          jj_la1[21] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+                                   AstMod jjtn003 = new AstMod(JJTMOD);
+                                   boolean jjtc003 = true;
+                                   jjtree.openNodeScope(jjtn003);
+        try {
+          Unary();
+        } catch (Throwable jjte003) {
+                                   if (jjtc003) {
+                                     jjtree.clearNodeScope(jjtn003);
+                                     jjtc003 = false;
+                                   } else {
+                                     jjtree.popNode();
+                                   }
+                                   if (jjte003 instanceof RuntimeException) {
+                                     {if (true) throw (RuntimeException)jjte003;}
+                                   }
+                                   if (jjte003 instanceof ParseException) {
+                                     {if (true) throw (ParseException)jjte003;}
+                                   }
+                                   {if (true) throw (Error)jjte003;}
+        } finally {
+                                   if (jjtc003) {
+                                     jjtree.closeNodeScope(jjtn003,  2);
+                                   }
+        }
+        break;
+      default:
+        jj_la1[22] = jj_gen;
+        jj_consume_token(-1);
+        throw new ParseException();
+      }
+    }
+  }
+
+/*
+ * Unary
+ * For '-' '!' 'not' 'empty', then Value
+ */
+  final public void Unary() throws ParseException {
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case MINUS:
+      jj_consume_token(MINUS);
+                          AstNegative jjtn001 = new AstNegative(JJTNEGATIVE);
+                          boolean jjtc001 = true;
+                          jjtree.openNodeScope(jjtn001);
+      try {
+        Unary();
+      } catch (Throwable jjte001) {
+                          if (jjtc001) {
+                            jjtree.clearNodeScope(jjtn001);
+                            jjtc001 = false;
+                          } else {
+                            jjtree.popNode();
+                          }
+                          if (jjte001 instanceof RuntimeException) {
+                            {if (true) throw (RuntimeException)jjte001;}
+                          }
+                          if (jjte001 instanceof ParseException) {
+                            {if (true) throw (ParseException)jjte001;}
+                          }
+                          {if (true) throw (Error)jjte001;}
+      } finally {
+                          if (jjtc001) {
+                            jjtree.closeNodeScope(jjtn001, true);
+                          }
+      }
+      break;
+    case NOT0:
+    case NOT1:
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case NOT0:
+        jj_consume_token(NOT0);
+        break;
+      case NOT1:
+        jj_consume_token(NOT1);
+        break;
+      default:
+        jj_la1[23] = jj_gen;
+        jj_consume_token(-1);
+        throw new ParseException();
+      }
+                                  AstNot jjtn002 = new AstNot(JJTNOT);
+                                  boolean jjtc002 = true;
+                                  jjtree.openNodeScope(jjtn002);
+      try {
+        Unary();
+      } catch (Throwable jjte002) {
+                                  if (jjtc002) {
+                                    jjtree.clearNodeScope(jjtn002);
+                                    jjtc002 = false;
+                                  } else {
+                                    jjtree.popNode();
+                                  }
+                                  if (jjte002 instanceof RuntimeException) {
+                                    {if (true) throw (RuntimeException)jjte002;}
+                                  }
+                                  if (jjte002 instanceof ParseException) {
+                                    {if (true) throw (ParseException)jjte002;}
+                                  }
+                                  {if (true) throw (Error)jjte002;}
+      } finally {
+                                  if (jjtc002) {
+                                    jjtree.closeNodeScope(jjtn002, true);
+                                  }
+      }
+      break;
+    case EMPTY:
+      jj_consume_token(EMPTY);
+                          AstEmpty jjtn003 = new AstEmpty(JJTEMPTY);
+                          boolean jjtc003 = true;
+                          jjtree.openNodeScope(jjtn003);
+      try {
+        Unary();
+      } catch (Throwable jjte003) {
+                          if (jjtc003) {
+                            jjtree.clearNodeScope(jjtn003);
+                            jjtc003 = false;
+                          } else {
+                            jjtree.popNode();
+                          }
+                          if (jjte003 instanceof RuntimeException) {
+                            {if (true) throw (RuntimeException)jjte003;}
+                          }
+                          if (jjte003 instanceof ParseException) {
+                            {if (true) throw (ParseException)jjte003;}
+                          }
+                          {if (true) throw (Error)jjte003;}
+      } finally {
+                          if (jjtc003) {
+                            jjtree.closeNodeScope(jjtn003, true);
+                          }
+      }
+      break;
+    case INTEGER_LITERAL:
+    case FLOATING_POINT_LITERAL:
+    case STRING_LITERAL:
+    case TRUE:
+    case FALSE:
+    case NULL:
+    case LPAREN:
+    case IDENTIFIER:
+      Value();
+      break;
+    default:
+      jj_la1[24] = jj_gen;
+      jj_consume_token(-1);
+      throw new ParseException();
+    }
+  }
+
+/*
+ * Value
+ * Defines Prefix plus zero or more Suffixes
+ */
+  final public void Value() throws ParseException {
+          AstValue jjtn001 = new AstValue(JJTVALUE);
+          boolean jjtc001 = true;
+          jjtree.openNodeScope(jjtn001);
+    try {
+      ValuePrefix();
+      label_9:
+      while (true) {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case DOT:
+        case LBRACK:
+          ;
+          break;
+        default:
+          jj_la1[25] = jj_gen;
+          break label_9;
+        }
+        ValueSuffix();
+      }
+    } catch (Throwable jjte001) {
+          if (jjtc001) {
+            jjtree.clearNodeScope(jjtn001);
+            jjtc001 = false;
+          } else {
+            jjtree.popNode();
+          }
+          if (jjte001 instanceof RuntimeException) {
+            {if (true) throw (RuntimeException)jjte001;}
+          }
+          if (jjte001 instanceof ParseException) {
+            {if (true) throw (ParseException)jjte001;}
+          }
+          {if (true) throw (Error)jjte001;}
+    } finally {
+          if (jjtc001) {
+            jjtree.closeNodeScope(jjtn001, jjtree.nodeArity() > 1);
+          }
+    }
+  }
+
+/*
+ * ValuePrefix
+ * For Literals, Variables, and Functions
+ */
+  final public void ValuePrefix() throws ParseException {
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case INTEGER_LITERAL:
+    case FLOATING_POINT_LITERAL:
+    case STRING_LITERAL:
+    case TRUE:
+    case FALSE:
+    case NULL:
+      Literal();
+      break;
+    case LPAREN:
+    case IDENTIFIER:
+      NonLiteral();
+      break;
+    default:
+      jj_la1[26] = jj_gen;
+      jj_consume_token(-1);
+      throw new ParseException();
+    }
+  }
+
+/*
+ * ValueSuffix
+ * Either dot or bracket notation
+ */
+  final public void ValueSuffix() throws ParseException {
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case DOT:
+      DotSuffix();
+      break;
+    case LBRACK:
+      BracketSuffix();
+      break;
+    default:
+      jj_la1[27] = jj_gen;
+      jj_consume_token(-1);
+      throw new ParseException();
+    }
+  }
+
+/*
+ * DotSuffix
+ * Dot Property
+ */
+  final public void DotSuffix() throws ParseException {
+                               /*@bgen(jjtree) DotSuffix */
+                                AstDotSuffix jjtn000 = new AstDotSuffix(JJTDOTSUFFIX);
+                                boolean jjtc000 = true;
+                                jjtree.openNodeScope(jjtn000);Token t = null;
+    try {
+      jj_consume_token(DOT);
+      t = jj_consume_token(IDENTIFIER);
+                               jjtree.closeNodeScope(jjtn000, true);
+                               jjtc000 = false;
+                               jjtn000.setImage(t.image);
+    } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+          }
+    }
+  }
+
+/*
+ * BracketSuffix
+ * Sub Expression Suffix
+ */
+  final public void BracketSuffix() throws ParseException {
+                                       /*@bgen(jjtree) BracketSuffix */
+  AstBracketSuffix jjtn000 = new AstBracketSuffix(JJTBRACKETSUFFIX);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+    try {
+      jj_consume_token(LBRACK);
+      Expression();
+      jj_consume_token(RBRACK);
+    } catch (Throwable jjte000) {
+          if (jjtc000) {
+            jjtree.clearNodeScope(jjtn000);
+            jjtc000 = false;
+          } else {
+            jjtree.popNode();
+          }
+          if (jjte000 instanceof RuntimeException) {
+            {if (true) throw (RuntimeException)jjte000;}
+          }
+          if (jjte000 instanceof ParseException) {
+            {if (true) throw (ParseException)jjte000;}
+          }
+          {if (true) throw (Error)jjte000;}
+    } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+          }
+    }
+  }
+
+/*
+ * NonLiteral
+ * For Grouped Operations, Identifiers, and Functions
+ */
+  final public void NonLiteral() throws ParseException {
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case LPAREN:
+      jj_consume_token(LPAREN);
+      Expression();
+      jj_consume_token(RPAREN);
+      break;
+    default:
+      jj_la1[28] = jj_gen;
+      if (jj_2_1(2)) {
+        Function();
+      } else {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case IDENTIFIER:
+          Identifier();
+          break;
+        default:
+          jj_la1[29] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+      }
+    }
+  }
+
+/*
+ * Identifier
+ * Java Language Identifier
+ */
+  final public void Identifier() throws ParseException {
+                                 /*@bgen(jjtree) Identifier */
+                                  AstIdentifier jjtn000 = new AstIdentifier(JJTIDENTIFIER);
+                                  boolean jjtc000 = true;
+                                  jjtree.openNodeScope(jjtn000);Token t = null;
+    try {
+      t = jj_consume_token(IDENTIFIER);
+                         jjtree.closeNodeScope(jjtn000, true);
+                         jjtc000 = false;
+                         jjtn000.setImage(t.image);
+    } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+          }
+    }
+  }
+
+/*
+ * Function
+ * Namespace:Name(a,b,c)
+ */
+  final public void Function() throws ParseException {
+ /*@bgen(jjtree) Function */
+        AstFunction jjtn000 = new AstFunction(JJTFUNCTION);
+        boolean jjtc000 = true;
+        jjtree.openNodeScope(jjtn000);Token t0 = null;
+        Token t1 = null;
+    try {
+      t0 = jj_consume_token(IDENTIFIER);
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case FUNCTIONSUFFIX:
+        t1 = jj_consume_token(FUNCTIONSUFFIX);
+        break;
+      default:
+        jj_la1[30] = jj_gen;
+        ;
+      }
+                if (t1 != null) {
+                        jjtn000.setPrefix(t0.image);
+                        jjtn000.setLocalName(t1.image.substring(1));
+                } else {
+                        jjtn000.setLocalName(t0.image);
+                }
+      jj_consume_token(LPAREN);
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case INTEGER_LITERAL:
+      case FLOATING_POINT_LITERAL:
+      case STRING_LITERAL:
+      case TRUE:
+      case FALSE:
+      case NULL:
+      case LPAREN:
+      case NOT0:
+      case NOT1:
+      case EMPTY:
+      case MINUS:
+      case IDENTIFIER:
+        Expression();
+        label_10:
+        while (true) {
+          switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+          case COMMA:
+            ;
+            break;
+          default:
+            jj_la1[31] = jj_gen;
+            break label_10;
+          }
+          jj_consume_token(COMMA);
+          Expression();
+        }
+        break;
+      default:
+        jj_la1[32] = jj_gen;
+        ;
+      }
+      jj_consume_token(RPAREN);
+    } catch (Throwable jjte000) {
+          if (jjtc000) {
+            jjtree.clearNodeScope(jjtn000);
+            jjtc000 = false;
+          } else {
+            jjtree.popNode();
+          }
+          if (jjte000 instanceof RuntimeException) {
+            {if (true) throw (RuntimeException)jjte000;}
+          }
+          if (jjte000 instanceof ParseException) {
+            {if (true) throw (ParseException)jjte000;}
+          }
+          {if (true) throw (Error)jjte000;}
+    } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+          }
+    }
+  }
+
+/*
+ * Literal
+ * Reserved Keywords
+ */
+  final public void Literal() throws ParseException {
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case TRUE:
+    case FALSE:
+      Boolean();
+      break;
+    case FLOATING_POINT_LITERAL:
+      FloatingPoint();
+      break;
+    case INTEGER_LITERAL:
+      Integer();
+      break;
+    case STRING_LITERAL:
+      String();
+      break;
+    case NULL:
+      Null();
+      break;
+    default:
+      jj_la1[33] = jj_gen;
+      jj_consume_token(-1);
+      throw new ParseException();
+    }
+  }
+
+/*
+ * Boolean
+ * For 'true' 'false'
+ */
+  final public void Boolean() throws ParseException {
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case TRUE:
+          AstTrue jjtn001 = new AstTrue(JJTTRUE);
+          boolean jjtc001 = true;
+          jjtree.openNodeScope(jjtn001);
+      try {
+        jj_consume_token(TRUE);
+      } finally {
+          if (jjtc001) {
+            jjtree.closeNodeScope(jjtn001, true);
+          }
+      }
+      break;
+    case FALSE:
+            AstFalse jjtn002 = new AstFalse(JJTFALSE);
+            boolean jjtc002 = true;
+            jjtree.openNodeScope(jjtn002);
+      try {
+        jj_consume_token(FALSE);
+      } finally {
+            if (jjtc002) {
+              jjtree.closeNodeScope(jjtn002, true);
+            }
+      }
+      break;
+    default:
+      jj_la1[34] = jj_gen;
+      jj_consume_token(-1);
+      throw new ParseException();
+    }
+  }
+
+/*
+ * FloatinPoint
+ * For Decimal and Floating Point Literals
+ */
+  final public void FloatingPoint() throws ParseException {
+                                       /*@bgen(jjtree) FloatingPoint */
+                                        AstFloatingPoint jjtn000 = new AstFloatingPoint(JJTFLOATINGPOINT);
+                                        boolean jjtc000 = true;
+                                        jjtree.openNodeScope(jjtn000);Token t = null;
+    try {
+      t = jj_consume_token(FLOATING_POINT_LITERAL);
+                                     jjtree.closeNodeScope(jjtn000, true);
+                                     jjtc000 = false;
+                                     jjtn000.setImage(t.image);
+    } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+          }
+    }
+  }
+
+/*
+ * Integer
+ * For Simple Numeric Literals
+ */
+  final public void Integer() throws ParseException {
+                           /*@bgen(jjtree) Integer */
+                            AstInteger jjtn000 = new AstInteger(JJTINTEGER);
+                            boolean jjtc000 = true;
+                            jjtree.openNodeScope(jjtn000);Token t = null;
+    try {
+      t = jj_consume_token(INTEGER_LITERAL);
+                              jjtree.closeNodeScope(jjtn000, true);
+                              jjtc000 = false;
+                              jjtn000.setImage(t.image);
+    } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+          }
+    }
+  }
+
+/*
+ * String
+ * For Quoted Literals
+ */
+  final public void String() throws ParseException {
+                         /*@bgen(jjtree) String */
+                          AstString jjtn000 = new AstString(JJTSTRING);
+                          boolean jjtc000 = true;
+                          jjtree.openNodeScope(jjtn000);Token t = null;
+    try {
+      t = jj_consume_token(STRING_LITERAL);
+                             jjtree.closeNodeScope(jjtn000, true);
+                             jjtc000 = false;
+                             jjtn000.setImage(t.image);
+    } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+          }
+    }
+  }
+
+/*
+ * Null
+ * For 'null'
+ */
+  final public void Null() throws ParseException {
+                     /*@bgen(jjtree) Null */
+  AstNull jjtn000 = new AstNull(JJTNULL);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+    try {
+      jj_consume_token(NULL);
+    } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+          }
+    }
+  }
+
+  final private boolean jj_2_1(int xla) {
+    jj_la = xla; jj_lastpos = jj_scanpos = token;
+    try { return !jj_3_1(); }
+    catch(LookaheadSuccess ls) { return true; }
+    finally { jj_save(0, xla); }
+  }
+
+  final private boolean jj_3_1() {
+    if (jj_3R_11()) return true;
+    return false;
+  }
+
+  final private boolean jj_3R_11() {
+    if (jj_scan_token(IDENTIFIER)) return true;
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_scan_token(54)) jj_scanpos = xsp;
+    if (jj_scan_token(LPAREN)) return true;
+    return false;
+  }
+
+  public ELParserTokenManager token_source;
+  SimpleCharStream jj_input_stream;
+  public Token token, jj_nt;
+  private int jj_ntk;
+  private Token jj_scanpos, jj_lastpos;
+  private int jj_la;
+  public boolean lookingAhead = false;
+  private boolean jj_semLA;
+  private int jj_gen;
+  final private int[] jj_la1 = new int[35];
+  static private int[] jj_la1_0;
+  static private int[] jj_la1_1;
+  static {
+      jj_la1_0();
+      jj_la1_1();
+   }
+   private static void jj_la1_0() {
+      jj_la1_0 = new int[] {0xe,0xe,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfe000000,0x18000000,0x6000000,0x80000000,0x60000000,0xfe000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x9d600,0x240000,0x9d600,0x240000,0x80000,0x0,0x0,0x1000000,0x9d600,0x1d600,0xc000,};
+   }
+   private static void jj_la1_1() {
+      jj_la1_1 = new int[] {0x0,0x0,0x10000,0x600,0x600,0x180,0x180,0x1e,0x6,0x18,0x1e,0x1,0x0,0x0,0x1,0x0,0x1,0xc000,0xc000,0x1e2000,0x60000,0x180000,0x1e2000,0x60,0x208860,0x0,0x200000,0x0,0x0,0x200000,0x400000,0x0,0x208860,0x0,0x0,};
+   }
+  final private JJCalls[] jj_2_rtns = new JJCalls[1];
+  private boolean jj_rescan = false;
+  private int jj_gc = 0;
+
+  public ELParser(java.io.InputStream stream) {
+    jj_input_stream = new SimpleCharStream(stream, 1, 1);
+    token_source = new ELParserTokenManager(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 35; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  public void ReInit(java.io.InputStream stream) {
+    jj_input_stream.ReInit(stream, 1, 1);
+    token_source.ReInit(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jjtree.reset();
+    jj_gen = 0;
+    for (int i = 0; i < 35; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  public ELParser(java.io.Reader stream) {
+    jj_input_stream = new SimpleCharStream(stream, 1, 1);
+    token_source = new ELParserTokenManager(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 35; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  public void ReInit(java.io.Reader stream) {
+    jj_input_stream.ReInit(stream, 1, 1);
+    token_source.ReInit(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jjtree.reset();
+    jj_gen = 0;
+    for (int i = 0; i < 35; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  public ELParser(ELParserTokenManager tm) {
+    token_source = tm;
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 35; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  public void ReInit(ELParserTokenManager tm) {
+    token_source = tm;
+    token = new Token();
+    jj_ntk = -1;
+    jjtree.reset();
+    jj_gen = 0;
+    for (int i = 0; i < 35; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  final private Token jj_consume_token(int kind) throws ParseException {
+    Token oldToken;
+    if ((oldToken = token).next != null) token = token.next;
+    else token = token.next = token_source.getNextToken();
+    jj_ntk = -1;
+    if (token.kind == kind) {
+      jj_gen++;
+      if (++jj_gc > 100) {
+        jj_gc = 0;
+        for (int i = 0; i < jj_2_rtns.length; i++) {
+          JJCalls c = jj_2_rtns[i];
+          while (c != null) {
+            if (c.gen < jj_gen) c.first = null;
+            c = c.next;
+          }
+        }
+      }
+      return token;
+    }
+    token = oldToken;
+    jj_kind = kind;
+    throw generateParseException();
+  }
+
+  static private final class LookaheadSuccess extends java.lang.Error { }
+  final private LookaheadSuccess jj_ls = new LookaheadSuccess();
+  final private boolean jj_scan_token(int kind) {
+    if (jj_scanpos == jj_lastpos) {
+      jj_la--;
+      if (jj_scanpos.next == null) {
+        jj_lastpos = jj_scanpos = jj_scanpos.next = token_source.getNextToken();
+      } else {
+        jj_lastpos = jj_scanpos = jj_scanpos.next;
+      }
+    } else {
+      jj_scanpos = jj_scanpos.next;
+    }
+    if (jj_rescan) {
+      int i = 0; Token tok = token;
+      while (tok != null && tok != jj_scanpos) { i++; tok = tok.next; }
+      if (tok != null) jj_add_error_token(kind, i);
+    }
+    if (jj_scanpos.kind != kind) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) throw jj_ls;
+    return false;
+  }
+
+  final public Token getNextToken() {
+    if (token.next != null) token = token.next;
+    else token = token.next = token_source.getNextToken();
+    jj_ntk = -1;
+    jj_gen++;
+    return token;
+  }
+
+  final public Token getToken(int index) {
+    Token t = lookingAhead ? jj_scanpos : token;
+    for (int i = 0; i < index; i++) {
+      if (t.next != null) t = t.next;
+      else t = t.next = token_source.getNextToken();
+    }
+    return t;
+  }
+
+  final private int jj_ntk() {
+    if ((jj_nt=token.next) == null)
+      return (jj_ntk = (token.next=token_source.getNextToken()).kind);
+    else
+      return (jj_ntk = jj_nt.kind);
+  }
+
+  private java.util.Vector jj_expentries = new java.util.Vector();
+  private int[] jj_expentry;
+  private int jj_kind = -1;
+  private int[] jj_lasttokens = new int[100];
+  private int jj_endpos;
+
+  private void jj_add_error_token(int kind, int pos) {
+    if (pos >= 100) return;
+    if (pos == jj_endpos + 1) {
+      jj_lasttokens[jj_endpos++] = kind;
+    } else if (jj_endpos != 0) {
+      jj_expentry = new int[jj_endpos];
+      for (int i = 0; i < jj_endpos; i++) {
+        jj_expentry[i] = jj_lasttokens[i];
+      }
+      boolean exists = false;
+      for (java.util.Enumeration e = jj_expentries.elements(); e.hasMoreElements();) {
+        int[] oldentry = (int[])(e.nextElement());
+        if (oldentry.length == jj_expentry.length) {
+          exists = true;
+          for (int i = 0; i < jj_expentry.length; i++) {
+            if (oldentry[i] != jj_expentry[i]) {
+              exists = false;
+              break;
+            }
+          }
+          if (exists) break;
+        }
+      }
+      if (!exists) jj_expentries.addElement(jj_expentry);
+      if (pos != 0) jj_lasttokens[(jj_endpos = pos) - 1] = kind;
+    }
+  }
+
+  public ParseException generateParseException() {
+    jj_expentries.removeAllElements();
+    boolean[] la1tokens = new boolean[59];
+    for (int i = 0; i < 59; i++) {
+      la1tokens[i] = false;
+    }
+    if (jj_kind >= 0) {
+      la1tokens[jj_kind] = true;
+      jj_kind = -1;
+    }
+    for (int i = 0; i < 35; i++) {
+      if (jj_la1[i] == jj_gen) {
+        for (int j = 0; j < 32; j++) {
+          if ((jj_la1_0[i] & (1<<j)) != 0) {
+            la1tokens[j] = true;
+          }
+          if ((jj_la1_1[i] & (1<<j)) != 0) {
+            la1tokens[32+j] = true;
+          }
+        }
+      }
+    }
+    for (int i = 0; i < 59; i++) {
+      if (la1tokens[i]) {
+        jj_expentry = new int[1];
+        jj_expentry[0] = i;
+        jj_expentries.addElement(jj_expentry);
+      }
+    }
+    jj_endpos = 0;
+    jj_rescan_token();
+    jj_add_error_token(0, 0);
+    int[][] exptokseq = new int[jj_expentries.size()][];
+    for (int i = 0; i < jj_expentries.size(); i++) {
+      exptokseq[i] = (int[])jj_expentries.elementAt(i);
+    }
+    return new ParseException(token, exptokseq, tokenImage);
+  }
+
+  final public void enable_tracing() {
+  }
+
+  final public void disable_tracing() {
+  }
+
+  final private void jj_rescan_token() {
+    jj_rescan = true;
+    for (int i = 0; i < 1; i++) {
+      JJCalls p = jj_2_rtns[i];
+      do {
+        if (p.gen > jj_gen) {
+          jj_la = p.arg; jj_lastpos = jj_scanpos = p.first;
+          switch (i) {
+            case 0: jj_3_1(); break;
+          }
+        }
+        p = p.next;
+      } while (p != null);
+    }
+    jj_rescan = false;
+  }
+
+  final private void jj_save(int index, int xla) {
+    JJCalls p = jj_2_rtns[index];
+    while (p.gen > jj_gen) {
+      if (p.next == null) { p = p.next = new JJCalls(); break; }
+      p = p.next;
+    }
+    p.gen = jj_gen + xla - jj_la; p.first = token; p.arg = xla;
+  }
+
+  static final class JJCalls {
+    int gen;
+    Token first;
+    int arg;
+    JJCalls next;
+  }
+
+}
diff --git a/src/org/jdesktop/el/impl/parser/ELParserConstants.java b/src/org/jdesktop/el/impl/parser/ELParserConstants.java
new file mode 100644
index 0000000..6d5696e
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/ELParserConstants.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree&JavaCC: Do not edit this line. ELParserConstants.java */
+package org.jdesktop.el.impl.parser;
+
+public interface ELParserConstants {
+
+  int EOF = 0;
+  int LITERAL_EXPRESSION = 1;
+  int START_DYNAMIC_EXPRESSION = 2;
+  int START_DEFERRED_EXPRESSION = 3;
+  int INTEGER_LITERAL = 9;
+  int FLOATING_POINT_LITERAL = 10;
+  int EXPONENT = 11;
+  int STRING_LITERAL = 12;
+  int BADLY_ESCAPED_STRING_LITERAL = 13;
+  int TRUE = 14;
+  int FALSE = 15;
+  int NULL = 16;
+  int END_EXPRESSION = 17;
+  int DOT = 18;
+  int LPAREN = 19;
+  int RPAREN = 20;
+  int LBRACK = 21;
+  int RBRACK = 22;
+  int COLON = 23;
+  int COMMA = 24;
+  int GT0 = 25;
+  int GT1 = 26;
+  int LT0 = 27;
+  int LT1 = 28;
+  int GE0 = 29;
+  int GE1 = 30;
+  int LE0 = 31;
+  int LE1 = 32;
+  int EQ0 = 33;
+  int EQ1 = 34;
+  int NE0 = 35;
+  int NE1 = 36;
+  int NOT0 = 37;
+  int NOT1 = 38;
+  int AND0 = 39;
+  int AND1 = 40;
+  int OR0 = 41;
+  int OR1 = 42;
+  int EMPTY = 43;
+  int INSTANCEOF = 44;
+  int MULT = 45;
+  int PLUS = 46;
+  int MINUS = 47;
+  int QUESTIONMARK = 48;
+  int DIV0 = 49;
+  int DIV1 = 50;
+  int MOD0 = 51;
+  int MOD1 = 52;
+  int IDENTIFIER = 53;
+  int FUNCTIONSUFFIX = 54;
+  int IMPL_OBJ_START = 55;
+  int LETTER = 56;
+  int DIGIT = 57;
+  int ILLEGAL_CHARACTER = 58;
+
+  int DEFAULT = 0;
+  int IN_EXPRESSION = 1;
+
+  String[] tokenImage = {
+    "<EOF>",
+    "<LITERAL_EXPRESSION>",
+    "\"${\"",
+    "\"#{\"",
+    "\"\\\\\"",
+    "\" \"",
+    "\"\\t\"",
+    "\"\\n\"",
+    "\"\\r\"",
+    "<INTEGER_LITERAL>",
+    "<FLOATING_POINT_LITERAL>",
+    "<EXPONENT>",
+    "<STRING_LITERAL>",
+    "<BADLY_ESCAPED_STRING_LITERAL>",
+    "\"true\"",
+    "\"false\"",
+    "\"null\"",
+    "\"}\"",
+    "\".\"",
+    "\"(\"",
+    "\")\"",
+    "\"[\"",
+    "\"]\"",
+    "\":\"",
+    "\",\"",
+    "\">\"",
+    "\"gt\"",
+    "\"<\"",
+    "\"lt\"",
+    "\">=\"",
+    "\"ge\"",
+    "\"<=\"",
+    "\"le\"",
+    "\"==\"",
+    "\"eq\"",
+    "\"!=\"",
+    "\"ne\"",
+    "\"!\"",
+    "\"not\"",
+    "\"&&\"",
+    "\"and\"",
+    "\"||\"",
+    "\"or\"",
+    "\"empty\"",
+    "\"instanceof\"",
+    "\"*\"",
+    "\"+\"",
+    "\"-\"",
+    "\"?\"",
+    "\"/\"",
+    "\"div\"",
+    "\"%\"",
+    "\"mod\"",
+    "<IDENTIFIER>",
+    "<FUNCTIONSUFFIX>",
+    "\"#\"",
+    "<LETTER>",
+    "<DIGIT>",
+    "<ILLEGAL_CHARACTER>",
+  };
+
+}
diff --git a/src/org/jdesktop/el/impl/parser/ELParserTokenManager.java b/src/org/jdesktop/el/impl/parser/ELParserTokenManager.java
new file mode 100644
index 0000000..442ffd0
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/ELParserTokenManager.java
@@ -0,0 +1,1285 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree&JavaCC: Do not edit this line. ELParserTokenManager.java */
+package org.jdesktop.el.impl.parser;
+import java.io.StringReader;
+import org.jdesktop.el.ELException;
+
+public class ELParserTokenManager implements ELParserConstants
+{
+  public  java.io.PrintStream debugStream = System.out;
+  public  void setDebugStream(java.io.PrintStream ds) { debugStream = ds; }
+private final int jjStopStringLiteralDfa_0(int pos, long active0)
+{
+   switch (pos)
+   {
+      case 0:
+         if ((active0 & 0x10L) != 0L)
+            return 2;
+         if ((active0 & 0x4L) != 0L)
+         {
+            jjmatchedKind = 1;
+            return 4;
+         }
+         if ((active0 & 0x8L) != 0L)
+         {
+            jjmatchedKind = 1;
+            return 6;
+         }
+         return -1;
+      default :
+         return -1;
+   }
+}
+private final int jjStartNfa_0(int pos, long active0)
+{
+   return jjMoveNfa_0(jjStopStringLiteralDfa_0(pos, active0), pos + 1);
+}
+private final int jjStopAtPos(int pos, int kind)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   return pos + 1;
+}
+private final int jjStartNfaWithStates_0(int pos, int kind, int state)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) { return pos + 1; }
+   return jjMoveNfa_0(state, pos + 1);
+}
+private final int jjMoveStringLiteralDfa0_0()
+{
+   switch(curChar)
+   {
+      case 35:
+         return jjMoveStringLiteralDfa1_0(0x8L);
+      case 36:
+         return jjMoveStringLiteralDfa1_0(0x4L);
+      case 92:
+         return jjStartNfaWithStates_0(0, 4, 2);
+      default :
+         return jjMoveNfa_0(7, 0);
+   }
+}
+private final int jjMoveStringLiteralDfa1_0(long active0)
+{
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_0(0, active0);
+      return 1;
+   }
+   switch(curChar)
+   {
+      case 123:
+         if ((active0 & 0x4L) != 0L)
+            return jjStopAtPos(1, 2);
+         else if ((active0 & 0x8L) != 0L)
+            return jjStopAtPos(1, 3);
+         break;
+      default :
+         break;
+   }
+   return jjStartNfa_0(0, active0);
+}
+private final void jjCheckNAdd(int state)
+{
+   if (jjrounds[state] != jjround)
+   {
+      jjstateSet[jjnewStateCnt++] = state;
+      jjrounds[state] = jjround;
+   }
+}
+private final void jjAddStates(int start, int end)
+{
+   do {
+      jjstateSet[jjnewStateCnt++] = jjnextStates[start];
+   } while (start++ != end);
+}
+private final void jjCheckNAddTwoStates(int state1, int state2)
+{
+   jjCheckNAdd(state1);
+   jjCheckNAdd(state2);
+}
+private final void jjCheckNAddStates(int start, int end)
+{
+   do {
+      jjCheckNAdd(jjnextStates[start]);
+   } while (start++ != end);
+}
+private final void jjCheckNAddStates(int start)
+{
+   jjCheckNAdd(jjnextStates[start]);
+   jjCheckNAdd(jjnextStates[start + 1]);
+}
+static final long[] jjbitVec0 = {
+   0xfffffffffffffffeL, 0xffffffffffffffffL, 0xffffffffffffffffL, 0xffffffffffffffffL
+};
+static final long[] jjbitVec2 = {
+   0x0L, 0x0L, 0xffffffffffffffffL, 0xffffffffffffffffL
+};
+private final int jjMoveNfa_0(int startState, int curPos)
+{
+   int[] nextStates;
+   int startsAt = 0;
+   jjnewStateCnt = 8;
+   int i = 1;
+   jjstateSet[0] = startState;
+   int j, kind = 0x7fffffff;
+   for (;;)
+   {
+      if (++jjround == 0x7fffffff)
+         ReInitRounds();
+      if (curChar < 64)
+      {
+         long l = 1L << curChar;
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 7:
+                  if ((0xffffffe7ffffffffL & l) != 0L)
+                  {
+                     if (kind > 1)
+                        kind = 1;
+                     jjCheckNAddStates(0, 3);
+                  }
+                  else if ((0x1800000000L & l) != 0L)
+                  {
+                     if (kind > 1)
+                        kind = 1;
+                  }
+                  if (curChar == 35)
+                     jjstateSet[jjnewStateCnt++] = 6;
+                  else if (curChar == 36)
+                     jjstateSet[jjnewStateCnt++] = 4;
+                  break;
+               case 0:
+                  if ((0xffffffe7ffffffffL & l) == 0L)
+                     break;
+                  if (kind > 1)
+                     kind = 1;
+                  jjCheckNAddStates(0, 3);
+                  break;
+               case 2:
+                  if ((0x1800000000L & l) == 0L)
+                     break;
+                  if (kind > 1)
+                     kind = 1;
+                  jjCheckNAddStates(0, 3);
+                  break;
+               case 3:
+                  if (curChar == 36)
+                     jjstateSet[jjnewStateCnt++] = 4;
+                  break;
+               case 4:
+                  if ((0xffffffefffffffffL & l) == 0L)
+                     break;
+                  if (kind > 1)
+                     kind = 1;
+                  jjCheckNAddStates(0, 3);
+                  break;
+               case 5:
+                  if (curChar == 35)
+                     jjstateSet[jjnewStateCnt++] = 6;
+                  break;
+               case 6:
+                  if ((0xfffffff7ffffffffL & l) == 0L)
+                     break;
+                  if (kind > 1)
+                     kind = 1;
+                  jjCheckNAddStates(0, 3);
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else if (curChar < 128)
+      {
+         long l = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 7:
+                  if ((0xffffffffefffffffL & l) != 0L)
+                  {
+                     if (kind > 1)
+                        kind = 1;
+                     jjCheckNAddStates(0, 3);
+                  }
+                  else if (curChar == 92)
+                     jjstateSet[jjnewStateCnt++] = 2;
+                  break;
+               case 0:
+                  if ((0xffffffffefffffffL & l) == 0L)
+                     break;
+                  if (kind > 1)
+                     kind = 1;
+                  jjCheckNAddStates(0, 3);
+                  break;
+               case 1:
+                  if (curChar == 92)
+                     jjstateSet[jjnewStateCnt++] = 2;
+                  break;
+               case 2:
+                  if (curChar != 92)
+                     break;
+                  if (kind > 1)
+                     kind = 1;
+                  jjCheckNAddStates(0, 3);
+                  break;
+               case 4:
+               case 6:
+                  if ((0xf7ffffffffffffffL & l) == 0L)
+                     break;
+                  if (kind > 1)
+                     kind = 1;
+                  jjCheckNAddStates(0, 3);
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else
+      {
+         int hiByte = (int)(curChar >> 8);
+         int i1 = hiByte >> 6;
+         long l1 = 1L << (hiByte & 077);
+         int i2 = (curChar & 0xff) >> 6;
+         long l2 = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 7:
+               case 0:
+               case 4:
+               case 6:
+                  if (!jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     break;
+                  if (kind > 1)
+                     kind = 1;
+                  jjCheckNAddStates(0, 3);
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      if (kind != 0x7fffffff)
+      {
+         jjmatchedKind = kind;
+         jjmatchedPos = curPos;
+         kind = 0x7fffffff;
+      }
+      ++curPos;
+      if ((i = jjnewStateCnt) == (startsAt = 8 - (jjnewStateCnt = startsAt)))
+         return curPos;
+      try { curChar = input_stream.readChar(); }
+      catch(java.io.IOException e) { return curPos; }
+   }
+}
+private final int jjStopStringLiteralDfa_1(int pos, long active0)
+{
+   switch (pos)
+   {
+      case 0:
+         if ((active0 & 0x800000L) != 0L)
+            return 8;
+         if ((active0 & 0x40000L) != 0L)
+            return 1;
+         if ((active0 & 0x141d555401c000L) != 0L)
+         {
+            jjmatchedKind = 53;
+            return 6;
+         }
+         return -1;
+      case 1:
+         if ((active0 & 0x41554000000L) != 0L)
+            return 6;
+         if ((active0 & 0x1419400001c000L) != 0L)
+         {
+            jjmatchedKind = 53;
+            jjmatchedPos = 1;
+            return 6;
+         }
+         return -1;
+      case 2:
+         if ((active0 & 0x14014000000000L) != 0L)
+            return 6;
+         if ((active0 & 0x18000001c000L) != 0L)
+         {
+            jjmatchedKind = 53;
+            jjmatchedPos = 2;
+            return 6;
+         }
+         return -1;
+      case 3:
+         if ((active0 & 0x14000L) != 0L)
+            return 6;
+         if ((active0 & 0x180000008000L) != 0L)
+         {
+            jjmatchedKind = 53;
+            jjmatchedPos = 3;
+            return 6;
+         }
+         return -1;
+      case 4:
+         if ((active0 & 0x80000008000L) != 0L)
+            return 6;
+         if ((active0 & 0x100000000000L) != 0L)
+         {
+            jjmatchedKind = 53;
+            jjmatchedPos = 4;
+            return 6;
+         }
+         return -1;
+      case 5:
+         if ((active0 & 0x100000000000L) != 0L)
+         {
+            jjmatchedKind = 53;
+            jjmatchedPos = 5;
+            return 6;
+         }
+         return -1;
+      case 6:
+         if ((active0 & 0x100000000000L) != 0L)
+         {
+            jjmatchedKind = 53;
+            jjmatchedPos = 6;
+            return 6;
+         }
+         return -1;
+      case 7:
+         if ((active0 & 0x100000000000L) != 0L)
+         {
+            jjmatchedKind = 53;
+            jjmatchedPos = 7;
+            return 6;
+         }
+         return -1;
+      case 8:
+         if ((active0 & 0x100000000000L) != 0L)
+         {
+            jjmatchedKind = 53;
+            jjmatchedPos = 8;
+            return 6;
+         }
+         return -1;
+      default :
+         return -1;
+   }
+}
+private final int jjStartNfa_1(int pos, long active0)
+{
+   return jjMoveNfa_1(jjStopStringLiteralDfa_1(pos, active0), pos + 1);
+}
+private final int jjStartNfaWithStates_1(int pos, int kind, int state)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) { return pos + 1; }
+   return jjMoveNfa_1(state, pos + 1);
+}
+private final int jjMoveStringLiteralDfa0_1()
+{
+   switch(curChar)
+   {
+      case 33:
+         jjmatchedKind = 37;
+         return jjMoveStringLiteralDfa1_1(0x800000000L);
+      case 37:
+         return jjStopAtPos(0, 51);
+      case 38:
+         return jjMoveStringLiteralDfa1_1(0x8000000000L);
+      case 40:
+         return jjStopAtPos(0, 19);
+      case 41:
+         return jjStopAtPos(0, 20);
+      case 42:
+         return jjStopAtPos(0, 45);
+      case 43:
+         return jjStopAtPos(0, 46);
+      case 44:
+         return jjStopAtPos(0, 24);
+      case 45:
+         return jjStopAtPos(0, 47);
+      case 46:
+         return jjStartNfaWithStates_1(0, 18, 1);
+      case 47:
+         return jjStopAtPos(0, 49);
+      case 58:
+         return jjStartNfaWithStates_1(0, 23, 8);
+      case 60:
+         jjmatchedKind = 27;
+         return jjMoveStringLiteralDfa1_1(0x80000000L);
+      case 61:
+         return jjMoveStringLiteralDfa1_1(0x200000000L);
+      case 62:
+         jjmatchedKind = 25;
+         return jjMoveStringLiteralDfa1_1(0x20000000L);
+      case 63:
+         return jjStopAtPos(0, 48);
+      case 91:
+         return jjStopAtPos(0, 21);
+      case 93:
+         return jjStopAtPos(0, 22);
+      case 97:
+         return jjMoveStringLiteralDfa1_1(0x10000000000L);
+      case 100:
+         return jjMoveStringLiteralDfa1_1(0x4000000000000L);
+      case 101:
+         return jjMoveStringLiteralDfa1_1(0x80400000000L);
+      case 102:
+         return jjMoveStringLiteralDfa1_1(0x8000L);
+      case 103:
+         return jjMoveStringLiteralDfa1_1(0x44000000L);
+      case 105:
+         return jjMoveStringLiteralDfa1_1(0x100000000000L);
+      case 108:
+         return jjMoveStringLiteralDfa1_1(0x110000000L);
+      case 109:
+         return jjMoveStringLiteralDfa1_1(0x10000000000000L);
+      case 110:
+         return jjMoveStringLiteralDfa1_1(0x5000010000L);
+      case 111:
+         return jjMoveStringLiteralDfa1_1(0x40000000000L);
+      case 116:
+         return jjMoveStringLiteralDfa1_1(0x4000L);
+      case 124:
+         return jjMoveStringLiteralDfa1_1(0x20000000000L);
+      case 125:
+         return jjStopAtPos(0, 17);
+      default :
+         return jjMoveNfa_1(0, 0);
+   }
+}
+private final int jjMoveStringLiteralDfa1_1(long active0)
+{
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_1(0, active0);
+      return 1;
+   }
+   switch(curChar)
+   {
+      case 38:
+         if ((active0 & 0x8000000000L) != 0L)
+            return jjStopAtPos(1, 39);
+         break;
+      case 61:
+         if ((active0 & 0x20000000L) != 0L)
+            return jjStopAtPos(1, 29);
+         else if ((active0 & 0x80000000L) != 0L)
+            return jjStopAtPos(1, 31);
+         else if ((active0 & 0x200000000L) != 0L)
+            return jjStopAtPos(1, 33);
+         else if ((active0 & 0x800000000L) != 0L)
+            return jjStopAtPos(1, 35);
+         break;
+      case 97:
+         return jjMoveStringLiteralDfa2_1(active0, 0x8000L);
+      case 101:
+         if ((active0 & 0x40000000L) != 0L)
+            return jjStartNfaWithStates_1(1, 30, 6);
+         else if ((active0 & 0x100000000L) != 0L)
+            return jjStartNfaWithStates_1(1, 32, 6);
+         else if ((active0 & 0x1000000000L) != 0L)
+            return jjStartNfaWithStates_1(1, 36, 6);
+         break;
+      case 105:
+         return jjMoveStringLiteralDfa2_1(active0, 0x4000000000000L);
+      case 109:
+         return jjMoveStringLiteralDfa2_1(active0, 0x80000000000L);
+      case 110:
+         return jjMoveStringLiteralDfa2_1(active0, 0x110000000000L);
+      case 111:
+         return jjMoveStringLiteralDfa2_1(active0, 0x10004000000000L);
+      case 113:
+         if ((active0 & 0x400000000L) != 0L)
+            return jjStartNfaWithStates_1(1, 34, 6);
+         break;
+      case 114:
+         if ((active0 & 0x40000000000L) != 0L)
+            return jjStartNfaWithStates_1(1, 42, 6);
+         return jjMoveStringLiteralDfa2_1(active0, 0x4000L);
+      case 116:
+         if ((active0 & 0x4000000L) != 0L)
+            return jjStartNfaWithStates_1(1, 26, 6);
+         else if ((active0 & 0x10000000L) != 0L)
+            return jjStartNfaWithStates_1(1, 28, 6);
+         break;
+      case 117:
+         return jjMoveStringLiteralDfa2_1(active0, 0x10000L);
+      case 124:
+         if ((active0 & 0x20000000000L) != 0L)
+            return jjStopAtPos(1, 41);
+         break;
+      default :
+         break;
+   }
+   return jjStartNfa_1(0, active0);
+}
+private final int jjMoveStringLiteralDfa2_1(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_1(0, old0); 
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_1(1, active0);
+      return 2;
+   }
+   switch(curChar)
+   {
+      case 100:
+         if ((active0 & 0x10000000000L) != 0L)
+            return jjStartNfaWithStates_1(2, 40, 6);
+         else if ((active0 & 0x10000000000000L) != 0L)
+            return jjStartNfaWithStates_1(2, 52, 6);
+         break;
+      case 108:
+         return jjMoveStringLiteralDfa3_1(active0, 0x18000L);
+      case 112:
+         return jjMoveStringLiteralDfa3_1(active0, 0x80000000000L);
+      case 115:
+         return jjMoveStringLiteralDfa3_1(active0, 0x100000000000L);
+      case 116:
+         if ((active0 & 0x4000000000L) != 0L)
+            return jjStartNfaWithStates_1(2, 38, 6);
+         break;
+      case 117:
+         return jjMoveStringLiteralDfa3_1(active0, 0x4000L);
+      case 118:
+         if ((active0 & 0x4000000000000L) != 0L)
+            return jjStartNfaWithStates_1(2, 50, 6);
+         break;
+      default :
+         break;
+   }
+   return jjStartNfa_1(1, active0);
+}
+private final int jjMoveStringLiteralDfa3_1(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_1(1, old0); 
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_1(2, active0);
+      return 3;
+   }
+   switch(curChar)
+   {
+      case 101:
+         if ((active0 & 0x4000L) != 0L)
+            return jjStartNfaWithStates_1(3, 14, 6);
+         break;
+      case 108:
+         if ((active0 & 0x10000L) != 0L)
+            return jjStartNfaWithStates_1(3, 16, 6);
+         break;
+      case 115:
+         return jjMoveStringLiteralDfa4_1(active0, 0x8000L);
+      case 116:
+         return jjMoveStringLiteralDfa4_1(active0, 0x180000000000L);
+      default :
+         break;
+   }
+   return jjStartNfa_1(2, active0);
+}
+private final int jjMoveStringLiteralDfa4_1(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_1(2, old0); 
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_1(3, active0);
+      return 4;
+   }
+   switch(curChar)
+   {
+      case 97:
+         return jjMoveStringLiteralDfa5_1(active0, 0x100000000000L);
+      case 101:
+         if ((active0 & 0x8000L) != 0L)
+            return jjStartNfaWithStates_1(4, 15, 6);
+         break;
+      case 121:
+         if ((active0 & 0x80000000000L) != 0L)
+            return jjStartNfaWithStates_1(4, 43, 6);
+         break;
+      default :
+         break;
+   }
+   return jjStartNfa_1(3, active0);
+}
+private final int jjMoveStringLiteralDfa5_1(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_1(3, old0); 
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_1(4, active0);
+      return 5;
+   }
+   switch(curChar)
+   {
+      case 110:
+         return jjMoveStringLiteralDfa6_1(active0, 0x100000000000L);
+      default :
+         break;
+   }
+   return jjStartNfa_1(4, active0);
+}
+private final int jjMoveStringLiteralDfa6_1(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_1(4, old0); 
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_1(5, active0);
+      return 6;
+   }
+   switch(curChar)
+   {
+      case 99:
+         return jjMoveStringLiteralDfa7_1(active0, 0x100000000000L);
+      default :
+         break;
+   }
+   return jjStartNfa_1(5, active0);
+}
+private final int jjMoveStringLiteralDfa7_1(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_1(5, old0); 
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_1(6, active0);
+      return 7;
+   }
+   switch(curChar)
+   {
+      case 101:
+         return jjMoveStringLiteralDfa8_1(active0, 0x100000000000L);
+      default :
+         break;
+   }
+   return jjStartNfa_1(6, active0);
+}
+private final int jjMoveStringLiteralDfa8_1(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_1(6, old0); 
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_1(7, active0);
+      return 8;
+   }
+   switch(curChar)
+   {
+      case 111:
+         return jjMoveStringLiteralDfa9_1(active0, 0x100000000000L);
+      default :
+         break;
+   }
+   return jjStartNfa_1(7, active0);
+}
+private final int jjMoveStringLiteralDfa9_1(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_1(7, old0); 
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_1(8, active0);
+      return 9;
+   }
+   switch(curChar)
+   {
+      case 102:
+         if ((active0 & 0x100000000000L) != 0L)
+            return jjStartNfaWithStates_1(9, 44, 6);
+         break;
+      default :
+         break;
+   }
+   return jjStartNfa_1(8, active0);
+}
+static final long[] jjbitVec3 = {
+   0x1ff00000fffffffeL, 0xffffffffffffc000L, 0xffffffffL, 0x600000000000000L
+};
+static final long[] jjbitVec4 = {
+   0x0L, 0x0L, 0x0L, 0xff7fffffff7fffffL
+};
+static final long[] jjbitVec5 = {
+   0x0L, 0xffffffffffffffffL, 0xffffffffffffffffL, 0xffffffffffffffffL
+};
+static final long[] jjbitVec6 = {
+   0xffffffffffffffffL, 0xffffffffffffffffL, 0xffffL, 0x0L
+};
+static final long[] jjbitVec7 = {
+   0xffffffffffffffffL, 0xffffffffffffffffL, 0x0L, 0x0L
+};
+static final long[] jjbitVec8 = {
+   0x3fffffffffffL, 0x0L, 0x0L, 0x0L
+};
+private final int jjMoveNfa_1(int startState, int curPos)
+{
+   int[] nextStates;
+   int startsAt = 0;
+   jjnewStateCnt = 38;
+   int i = 1;
+   jjstateSet[0] = startState;
+   int j, kind = 0x7fffffff;
+   for (;;)
+   {
+      if (++jjround == 0x7fffffff)
+         ReInitRounds();
+      if (curChar < 64)
+      {
+         long l = 1L << curChar;
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if ((0x3ff000000000000L & l) != 0L)
+                  {
+                     if (kind > 9)
+                        kind = 9;
+                     jjCheckNAddStates(4, 8);
+                  }
+                  else if ((0x1800000000L & l) != 0L)
+                  {
+                     if (kind > 53)
+                        kind = 53;
+                     jjCheckNAdd(6);
+                  }
+                  else if (curChar == 39)
+                     jjCheckNAddStates(9, 13);
+                  else if (curChar == 34)
+                     jjCheckNAddStates(14, 18);
+                  else if (curChar == 58)
+                     jjstateSet[jjnewStateCnt++] = 8;
+                  else if (curChar == 46)
+                     jjCheckNAdd(1);
+                  break;
+               case 1:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 10)
+                     kind = 10;
+                  jjCheckNAddTwoStates(1, 2);
+                  break;
+               case 3:
+                  if ((0x280000000000L & l) != 0L)
+                     jjCheckNAdd(4);
+                  break;
+               case 4:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 10)
+                     kind = 10;
+                  jjCheckNAdd(4);
+                  break;
+               case 5:
+                  if ((0x1800000000L & l) == 0L)
+                     break;
+                  if (kind > 53)
+                     kind = 53;
+                  jjCheckNAdd(6);
+                  break;
+               case 6:
+                  if ((0x3ff001000000000L & l) == 0L)
+                     break;
+                  if (kind > 53)
+                     kind = 53;
+                  jjCheckNAdd(6);
+                  break;
+               case 7:
+                  if (curChar == 58)
+                     jjstateSet[jjnewStateCnt++] = 8;
+                  break;
+               case 8:
+                  if ((0x1800000000L & l) == 0L)
+                     break;
+                  if (kind > 54)
+                     kind = 54;
+                  jjCheckNAdd(9);
+                  break;
+               case 9:
+                  if ((0x3ff001000000000L & l) == 0L)
+                     break;
+                  if (kind > 54)
+                     kind = 54;
+                  jjCheckNAdd(9);
+                  break;
+               case 10:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 9)
+                     kind = 9;
+                  jjCheckNAddStates(4, 8);
+                  break;
+               case 11:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 9)
+                     kind = 9;
+                  jjCheckNAdd(11);
+                  break;
+               case 12:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(12, 13);
+                  break;
+               case 13:
+                  if (curChar != 46)
+                     break;
+                  if (kind > 10)
+                     kind = 10;
+                  jjCheckNAddTwoStates(14, 15);
+                  break;
+               case 14:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 10)
+                     kind = 10;
+                  jjCheckNAddTwoStates(14, 15);
+                  break;
+               case 16:
+                  if ((0x280000000000L & l) != 0L)
+                     jjCheckNAdd(17);
+                  break;
+               case 17:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 10)
+                     kind = 10;
+                  jjCheckNAdd(17);
+                  break;
+               case 18:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(18, 19);
+                  break;
+               case 20:
+                  if ((0x280000000000L & l) != 0L)
+                     jjCheckNAdd(21);
+                  break;
+               case 21:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 10)
+                     kind = 10;
+                  jjCheckNAdd(21);
+                  break;
+               case 22:
+                  if (curChar == 34)
+                     jjCheckNAddStates(14, 18);
+                  break;
+               case 23:
+                  if ((0xfffffffbffffffffL & l) != 0L)
+                     jjCheckNAddStates(19, 21);
+                  break;
+               case 25:
+                  if (curChar == 34)
+                     jjCheckNAddStates(19, 21);
+                  break;
+               case 26:
+                  if (curChar == 34 && kind > 12)
+                     kind = 12;
+                  break;
+               case 27:
+                  if ((0xfffffffbffffffffL & l) != 0L)
+                     jjCheckNAddTwoStates(27, 28);
+                  break;
+               case 29:
+                  if ((0xfffffffbffffffffL & l) != 0L && kind > 13)
+                     kind = 13;
+                  break;
+               case 30:
+                  if (curChar == 39)
+                     jjCheckNAddStates(9, 13);
+                  break;
+               case 31:
+                  if ((0xffffff7fffffffffL & l) != 0L)
+                     jjCheckNAddStates(22, 24);
+                  break;
+               case 33:
+                  if (curChar == 39)
+                     jjCheckNAddStates(22, 24);
+                  break;
+               case 34:
+                  if (curChar == 39 && kind > 12)
+                     kind = 12;
+                  break;
+               case 35:
+                  if ((0xffffff7fffffffffL & l) != 0L)
+                     jjCheckNAddTwoStates(35, 36);
+                  break;
+               case 37:
+                  if ((0xffffff7fffffffffL & l) != 0L && kind > 13)
+                     kind = 13;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else if (curChar < 128)
+      {
+         long l = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+               case 6:
+                  if ((0x7fffffe87fffffeL & l) == 0L)
+                     break;
+                  if (kind > 53)
+                     kind = 53;
+                  jjCheckNAdd(6);
+                  break;
+               case 2:
+                  if ((0x2000000020L & l) != 0L)
+                     jjAddStates(25, 26);
+                  break;
+               case 8:
+               case 9:
+                  if ((0x7fffffe87fffffeL & l) == 0L)
+                     break;
+                  if (kind > 54)
+                     kind = 54;
+                  jjCheckNAdd(9);
+                  break;
+               case 15:
+                  if ((0x2000000020L & l) != 0L)
+                     jjAddStates(27, 28);
+                  break;
+               case 19:
+                  if ((0x2000000020L & l) != 0L)
+                     jjAddStates(29, 30);
+                  break;
+               case 23:
+                  if ((0xffffffffefffffffL & l) != 0L)
+                     jjCheckNAddStates(19, 21);
+                  break;
+               case 24:
+                  if (curChar == 92)
+                     jjstateSet[jjnewStateCnt++] = 25;
+                  break;
+               case 25:
+                  if (curChar == 92)
+                     jjCheckNAddStates(19, 21);
+                  break;
+               case 27:
+                  if ((0xffffffffefffffffL & l) != 0L)
+                     jjAddStates(31, 32);
+                  break;
+               case 28:
+                  if (curChar == 92)
+                     jjstateSet[jjnewStateCnt++] = 29;
+                  break;
+               case 29:
+               case 37:
+                  if ((0xffffffffefffffffL & l) != 0L && kind > 13)
+                     kind = 13;
+                  break;
+               case 31:
+                  if ((0xffffffffefffffffL & l) != 0L)
+                     jjCheckNAddStates(22, 24);
+                  break;
+               case 32:
+                  if (curChar == 92)
+                     jjstateSet[jjnewStateCnt++] = 33;
+                  break;
+               case 33:
+                  if (curChar == 92)
+                     jjCheckNAddStates(22, 24);
+                  break;
+               case 35:
+                  if ((0xffffffffefffffffL & l) != 0L)
+                     jjAddStates(33, 34);
+                  break;
+               case 36:
+                  if (curChar == 92)
+                     jjstateSet[jjnewStateCnt++] = 37;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else
+      {
+         int hiByte = (int)(curChar >> 8);
+         int i1 = hiByte >> 6;
+         long l1 = 1L << (hiByte & 077);
+         int i2 = (curChar & 0xff) >> 6;
+         long l2 = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+               case 6:
+                  if (!jjCanMove_1(hiByte, i1, i2, l1, l2))
+                     break;
+                  if (kind > 53)
+                     kind = 53;
+                  jjCheckNAdd(6);
+                  break;
+               case 8:
+               case 9:
+                  if (!jjCanMove_1(hiByte, i1, i2, l1, l2))
+                     break;
+                  if (kind > 54)
+                     kind = 54;
+                  jjCheckNAdd(9);
+                  break;
+               case 23:
+                  if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     jjAddStates(19, 21);
+                  break;
+               case 27:
+                  if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     jjAddStates(31, 32);
+                  break;
+               case 29:
+               case 37:
+                  if (jjCanMove_0(hiByte, i1, i2, l1, l2) && kind > 13)
+                     kind = 13;
+                  break;
+               case 31:
+                  if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     jjAddStates(22, 24);
+                  break;
+               case 35:
+                  if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+                     jjAddStates(33, 34);
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      if (kind != 0x7fffffff)
+      {
+         jjmatchedKind = kind;
+         jjmatchedPos = curPos;
+         kind = 0x7fffffff;
+      }
+      ++curPos;
+      if ((i = jjnewStateCnt) == (startsAt = 38 - (jjnewStateCnt = startsAt)))
+         return curPos;
+      try { curChar = input_stream.readChar(); }
+      catch(java.io.IOException e) { return curPos; }
+   }
+}
+static final int[] jjnextStates = {
+   0, 1, 3, 5, 11, 12, 13, 18, 19, 31, 32, 34, 35, 36, 23, 24, 
+   26, 27, 28, 23, 24, 26, 31, 32, 34, 3, 4, 16, 17, 20, 21, 27, 
+   28, 35, 36, 
+};
+private static final boolean jjCanMove_0(int hiByte, int i1, int i2, long l1, long l2)
+{
+   switch(hiByte)
+   {
+      case 0:
+         return ((jjbitVec2[i2] & l2) != 0L);
+      default : 
+         if ((jjbitVec0[i1] & l1) != 0L)
+            return true;
+         return false;
+   }
+}
+private static final boolean jjCanMove_1(int hiByte, int i1, int i2, long l1, long l2)
+{
+   switch(hiByte)
+   {
+      case 0:
+         return ((jjbitVec4[i2] & l2) != 0L);
+      case 48:
+         return ((jjbitVec5[i2] & l2) != 0L);
+      case 49:
+         return ((jjbitVec6[i2] & l2) != 0L);
+      case 51:
+         return ((jjbitVec7[i2] & l2) != 0L);
+      case 61:
+         return ((jjbitVec8[i2] & l2) != 0L);
+      default : 
+         if ((jjbitVec3[i1] & l1) != 0L)
+            return true;
+         return false;
+   }
+}
+public static final String[] jjstrLiteralImages = {
+"", null, "\44\173", "\43\173", null, null, null, null, null, null, null, null, 
+null, null, "\164\162\165\145", "\146\141\154\163\145", "\156\165\154\154", "\175", 
+"\56", "\50", "\51", "\133", "\135", "\72", "\54", "\76", "\147\164", "\74", 
+"\154\164", "\76\75", "\147\145", "\74\75", "\154\145", "\75\75", "\145\161", "\41\75", 
+"\156\145", "\41", "\156\157\164", "\46\46", "\141\156\144", "\174\174", "\157\162", 
+"\145\155\160\164\171", "\151\156\163\164\141\156\143\145\157\146", "\52", "\53", "\55", "\77", "\57", 
+"\144\151\166", "\45", "\155\157\144", null, null, null, null, null, null, };
+public static final String[] lexStateNames = {
+   "DEFAULT", 
+   "IN_EXPRESSION", 
+};
+public static final int[] jjnewLexState = {
+   -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1, -1, -1, 
+   -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+   -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+};
+static final long[] jjtoToken = {
+   0x47ffffffffff60fL, 
+};
+static final long[] jjtoSkip = {
+   0x1f0L, 
+};
+protected SimpleCharStream input_stream;
+private final int[] jjrounds = new int[38];
+private final int[] jjstateSet = new int[76];
+protected char curChar;
+public ELParserTokenManager(SimpleCharStream stream)
+{
+   if (SimpleCharStream.staticFlag)
+      throw new Error("ERROR: Cannot use a static CharStream class with a non-static lexical analyzer.");
+   input_stream = stream;
+}
+public ELParserTokenManager(SimpleCharStream stream, int lexState)
+{
+   this(stream);
+   SwitchTo(lexState);
+}
+public void ReInit(SimpleCharStream stream)
+{
+   jjmatchedPos = jjnewStateCnt = 0;
+   curLexState = defaultLexState;
+   input_stream = stream;
+   ReInitRounds();
+}
+private final void ReInitRounds()
+{
+   int i;
+   jjround = 0x80000001;
+   for (i = 38; i-- > 0;)
+      jjrounds[i] = 0x80000000;
+}
+public void ReInit(SimpleCharStream stream, int lexState)
+{
+   ReInit(stream);
+   SwitchTo(lexState);
+}
+public void SwitchTo(int lexState)
+{
+   if (lexState >= 2 || lexState < 0)
+      throw new TokenMgrError("Error: Ignoring invalid lexical state : " + lexState + ". State unchanged.", TokenMgrError.INVALID_LEXICAL_STATE);
+   else
+      curLexState = lexState;
+}
+
+protected Token jjFillToken()
+{
+   Token t = Token.newToken(jjmatchedKind);
+   t.kind = jjmatchedKind;
+   String im = jjstrLiteralImages[jjmatchedKind];
+   t.image = (im == null) ? input_stream.GetImage() : im;
+   t.beginLine = input_stream.getBeginLine();
+   t.beginColumn = input_stream.getBeginColumn();
+   t.endLine = input_stream.getEndLine();
+   t.endColumn = input_stream.getEndColumn();
+   return t;
+}
+
+int curLexState = 0;
+int defaultLexState = 0;
+int jjnewStateCnt;
+int jjround;
+int jjmatchedPos;
+int jjmatchedKind;
+
+public Token getNextToken() 
+{
+  int kind;
+  Token specialToken = null;
+  Token matchedToken;
+  int curPos = 0;
+
+  EOFLoop :
+  for (;;)
+  {   
+   try   
+   {     
+      curChar = input_stream.BeginToken();
+   }     
+   catch(java.io.IOException e)
+   {        
+      jjmatchedKind = 0;
+      matchedToken = jjFillToken();
+      return matchedToken;
+   }
+
+   switch(curLexState)
+   {
+     case 0:
+       jjmatchedKind = 0x7fffffff;
+       jjmatchedPos = 0;
+       curPos = jjMoveStringLiteralDfa0_0();
+       break;
+     case 1:
+       try { input_stream.backup(0);
+          while (curChar <= 32 && (0x100002600L & (1L << curChar)) != 0L)
+             curChar = input_stream.BeginToken();
+       }
+       catch (java.io.IOException e1) { continue EOFLoop; }
+       jjmatchedKind = 0x7fffffff;
+       jjmatchedPos = 0;
+       curPos = jjMoveStringLiteralDfa0_1();
+       if (jjmatchedPos == 0 && jjmatchedKind > 58)
+       {
+          jjmatchedKind = 58;
+       }
+       break;
+   }
+     if (jjmatchedKind != 0x7fffffff)
+     {
+        if (jjmatchedPos + 1 < curPos)
+           input_stream.backup(curPos - jjmatchedPos - 1);
+        if ((jjtoToken[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L)
+        {
+           matchedToken = jjFillToken();
+       if (jjnewLexState[jjmatchedKind] != -1)
+         curLexState = jjnewLexState[jjmatchedKind];
+           return matchedToken;
+        }
+        else
+        {
+         if (jjnewLexState[jjmatchedKind] != -1)
+           curLexState = jjnewLexState[jjmatchedKind];
+           continue EOFLoop;
+        }
+     }
+     int error_line = input_stream.getEndLine();
+     int error_column = input_stream.getEndColumn();
+     String error_after = null;
+     boolean EOFSeen = false;
+     try { input_stream.readChar(); input_stream.backup(1); }
+     catch (java.io.IOException e1) {
+        EOFSeen = true;
+        error_after = curPos <= 1 ? "" : input_stream.GetImage();
+        if (curChar == '\n' || curChar == '\r') {
+           error_line++;
+           error_column = 0;
+        }
+        else
+           error_column++;
+     }
+     if (!EOFSeen) {
+        input_stream.backup(1);
+        error_after = curPos <= 1 ? "" : input_stream.GetImage();
+     }
+     throw new TokenMgrError(EOFSeen, curLexState, error_line, error_column, error_after, curChar, TokenMgrError.LEXICAL_ERROR);
+  }
+}
+
+}
diff --git a/src/org/jdesktop/el/impl/parser/ELParserTreeConstants.java b/src/org/jdesktop/el/impl/parser/ELParserTreeConstants.java
new file mode 100644
index 0000000..ec1863c
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/ELParserTreeConstants.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. ELParserTreeConstants.java */
+
+package org.jdesktop.el.impl.parser;
+
+public interface ELParserTreeConstants
+{
+  public int JJTCOMPOSITEEXPRESSION = 0;
+  public int JJTLITERALEXPRESSION = 1;
+  public int JJTDEFERREDEXPRESSION = 2;
+  public int JJTDYNAMICEXPRESSION = 3;
+  public int JJTVOID = 4;
+  public int JJTCHOICE = 5;
+  public int JJTOR = 6;
+  public int JJTAND = 7;
+  public int JJTEQUAL = 8;
+  public int JJTNOTEQUAL = 9;
+  public int JJTLESSTHAN = 10;
+  public int JJTGREATERTHAN = 11;
+  public int JJTLESSTHANEQUAL = 12;
+  public int JJTGREATERTHANEQUAL = 13;
+  public int JJTPLUS = 14;
+  public int JJTMINUS = 15;
+  public int JJTMULT = 16;
+  public int JJTDIV = 17;
+  public int JJTMOD = 18;
+  public int JJTNEGATIVE = 19;
+  public int JJTNOT = 20;
+  public int JJTEMPTY = 21;
+  public int JJTVALUE = 22;
+  public int JJTDOTSUFFIX = 23;
+  public int JJTBRACKETSUFFIX = 24;
+  public int JJTIDENTIFIER = 25;
+  public int JJTFUNCTION = 26;
+  public int JJTTRUE = 27;
+  public int JJTFALSE = 28;
+  public int JJTFLOATINGPOINT = 29;
+  public int JJTINTEGER = 30;
+  public int JJTSTRING = 31;
+  public int JJTNULL = 32;
+
+
+  public String[] jjtNodeName = {
+    "CompositeExpression",
+    "LiteralExpression",
+    "DeferredExpression",
+    "DynamicExpression",
+    "void",
+    "Choice",
+    "Or",
+    "And",
+    "Equal",
+    "NotEqual",
+    "LessThan",
+    "GreaterThan",
+    "LessThanEqual",
+    "GreaterThanEqual",
+    "Plus",
+    "Minus",
+    "Mult",
+    "Div",
+    "Mod",
+    "Negative",
+    "Not",
+    "Empty",
+    "Value",
+    "DotSuffix",
+    "BracketSuffix",
+    "Identifier",
+    "Function",
+    "True",
+    "False",
+    "FloatingPoint",
+    "Integer",
+    "String",
+    "Null",
+  };
+}
diff --git a/src/org/jdesktop/el/impl/parser/JJTELParserState.java b/src/org/jdesktop/el/impl/parser/JJTELParserState.java
new file mode 100644
index 0000000..117dbaa
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/JJTELParserState.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. JJTELParserState.java */
+
+package org.jdesktop.el.impl.parser;
+
+class JJTELParserState {
+  private java.util.Stack nodes;
+  private java.util.Stack marks;
+
+  private int sp;		// number of nodes on stack
+  private int mk;		// current mark
+  private boolean node_created;
+
+  JJTELParserState() {
+    nodes = new java.util.Stack();
+    marks = new java.util.Stack();
+    sp = 0;
+    mk = 0;
+  }
+
+  /* Determines whether the current node was actually closed and
+     pushed.  This should only be called in the final user action of a
+     node scope.  */
+  boolean nodeCreated() {
+    return node_created;
+  }
+
+  /* Call this to reinitialize the node stack.  It is called
+     automatically by the parser's ReInit() method. */
+  void reset() {
+    nodes.removeAllElements();
+    marks.removeAllElements();
+    sp = 0;
+    mk = 0;
+  }
+
+  /* Returns the root node of the AST.  It only makes sense to call
+     this after a successful parse. */
+  Node rootNode() {
+    return (Node)nodes.elementAt(0);
+  }
+
+  /* Pushes a node on to the stack. */
+  void pushNode(Node n) {
+    nodes.push(n);
+    ++sp;
+  }
+
+  /* Returns the node on the top of the stack, and remove it from the
+     stack.  */
+  Node popNode() {
+    if (--sp < mk) {
+      mk = ((Integer)marks.pop()).intValue();
+    }
+    return (Node)nodes.pop();
+  }
+
+  /* Returns the node currently on the top of the stack. */
+  Node peekNode() {
+    return (Node)nodes.peek();
+  }
+
+  /* Returns the number of children on the stack in the current node
+     scope. */
+  int nodeArity() {
+    return sp - mk;
+  }
+
+
+  void clearNodeScope(Node n) {
+    while (sp > mk) {
+      popNode();
+    }
+    mk = ((Integer)marks.pop()).intValue();
+  }
+
+
+  void openNodeScope(Node n) {
+    marks.push(new Integer(mk));
+    mk = sp;
+    n.jjtOpen();
+  }
+
+
+  /* A definite node is constructed from a specified number of
+     children.  That number of nodes are popped from the stack and
+     made the children of the definite node.  Then the definite node
+     is pushed on to the stack. */
+  void closeNodeScope(Node n, int num) {
+    mk = ((Integer)marks.pop()).intValue();
+    while (num-- > 0) {
+      Node c = popNode();
+      c.jjtSetParent(n);
+      n.jjtAddChild(c, num);
+    }
+    n.jjtClose();
+    pushNode(n);
+    node_created = true;
+  }
+
+
+  /* A conditional node is constructed if its condition is true.  All
+     the nodes that have been pushed since the node was opened are
+     made children of the the conditional node, which is then pushed
+     on to the stack.  If the condition is false the node is not
+     constructed and they are left on the stack. */
+  void closeNodeScope(Node n, boolean condition) {
+    if (condition) {
+      int a = nodeArity();
+      mk = ((Integer)marks.pop()).intValue();
+      while (a-- > 0) {
+	Node c = popNode();
+	c.jjtSetParent(n);
+	n.jjtAddChild(c, a);
+      }
+      n.jjtClose();
+      pushNode(n);
+      node_created = true;
+    } else {
+      mk = ((Integer)marks.pop()).intValue();
+      node_created = false;
+    }
+  }
+}
diff --git a/src/org/jdesktop/el/impl/parser/Node.java b/src/org/jdesktop/el/impl/parser/Node.java
new file mode 100644
index 0000000..0de6a8d
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/Node.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. Node.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELException;
+import org.jdesktop.el.MethodInfo;
+
+import org.jdesktop.el.impl.lang.EvaluationContext;
+
+/* All AST nodes must implement this interface.  It provides basic
+   machinery for constructing the parent and child relationships
+   between nodes. */
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public interface Node {
+
+  /** This method is called after the node has been made the current
+    node.  It indicates that child nodes can now be added to it. */
+  public void jjtOpen();
+
+  /** This method is called after all the child nodes have been
+    added. */
+  public void jjtClose();
+
+  /** This pair of methods are used to inform the node of its
+    parent. */
+  public void jjtSetParent(Node n);
+  public Node jjtGetParent();
+
+  /** This method tells the node to add its argument to the node's
+    list of children.  */
+  public void jjtAddChild(Node n, int i);
+
+  /** This method returns a child node.  The children are numbered
+     from zero, left to right. */
+  public Node jjtGetChild(int i);
+
+  /** Return the number of children the node has. */
+  public int jjtGetNumChildren();
+  
+  public String getImage();
+  
+  public Object getValue(EvaluationContext ctx) throws ELException;
+  public void setValue(EvaluationContext ctx, Object value) throws ELException;
+  public Class getType(EvaluationContext ctx) throws ELException;
+  public boolean isReadOnly(EvaluationContext ctx) throws ELException;
+  public void accept(NodeVisitor visitor) throws ELException;
+  public MethodInfo getMethodInfo(EvaluationContext ctx, Class[] paramTypes) throws ELException;
+  public Object invoke(EvaluationContext ctx, Class[] paramTypes, Object[] paramValues) throws ELException;
+}
diff --git a/src/org/jdesktop/el/impl/parser/NodeVisitor.java b/src/org/jdesktop/el/impl/parser/NodeVisitor.java
new file mode 100644
index 0000000..b40c6cf
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/NodeVisitor.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELException;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public interface NodeVisitor {
+    public void visit(Node node) throws ELException;
+}
diff --git a/src/org/jdesktop/el/impl/parser/ParseException.java b/src/org/jdesktop/el/impl/parser/ParseException.java
new file mode 100644
index 0000000..f67100a
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/ParseException.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JavaCC: Do not edit this line. ParseException.java Version 3.0 */
+package org.jdesktop.el.impl.parser;
+
+/**
+ * This exception is thrown when parse errors are encountered.
+ * You can explicitly create objects of this exception type by
+ * calling the method generateParseException in the generated
+ * parser.
+ *
+ * You can modify this class to customize your error reporting
+ * mechanisms so long as you retain the public fields.
+ */
+public class ParseException extends Exception {
+
+  /**
+   * This constructor is used by the method "generateParseException"
+   * in the generated parser.  Calling this constructor generates
+   * a new object of this type with the fields "currentToken",
+   * "expectedTokenSequences", and "tokenImage" set.  The boolean
+   * flag "specialConstructor" is also set to true to indicate that
+   * this constructor was used to create this object.
+   * This constructor calls its super class with the empty string
+   * to force the "toString" method of parent class "Throwable" to
+   * print the error message in the form:
+   *     ParseException: <result of getMessage>
+   */
+  public ParseException(Token currentTokenVal,
+                        int[][] expectedTokenSequencesVal,
+                        String[] tokenImageVal
+                       )
+  {
+    super("");
+    specialConstructor = true;
+    currentToken = currentTokenVal;
+    expectedTokenSequences = expectedTokenSequencesVal;
+    tokenImage = tokenImageVal;
+  }
+
+  /**
+   * The following constructors are for use by you for whatever
+   * purpose you can think of.  Constructing the exception in this
+   * manner makes the exception behave in the normal way - i.e., as
+   * documented in the class "Throwable".  The fields "errorToken",
+   * "expectedTokenSequences", and "tokenImage" do not contain
+   * relevant information.  The JavaCC generated code does not use
+   * these constructors.
+   */
+
+  public ParseException() {
+    super();
+    specialConstructor = false;
+  }
+
+  public ParseException(String message) {
+    super(message);
+    specialConstructor = false;
+  }
+
+  /**
+   * This variable determines which constructor was used to create
+   * this object and thereby affects the semantics of the
+   * "getMessage" method (see below).
+   */
+  protected boolean specialConstructor;
+
+  /**
+   * This is the last token that has been consumed successfully.  If
+   * this object has been created due to a parse error, the token
+   * followng this token will (therefore) be the first error token.
+   */
+  public Token currentToken;
+
+  /**
+   * Each entry in this array is an array of integers.  Each array
+   * of integers represents a sequence of tokens (by their ordinal
+   * values) that is expected at this point of the parse.
+   */
+  public int[][] expectedTokenSequences;
+
+  /**
+   * This is a reference to the "tokenImage" array of the generated
+   * parser within which the parse error occurred.  This array is
+   * defined in the generated ...Constants interface.
+   */
+  public String[] tokenImage;
+
+  /**
+   * This method has the standard behavior when this object has been
+   * created using the standard constructors.  Otherwise, it uses
+   * "currentToken" and "expectedTokenSequences" to generate a parse
+   * error message and returns it.  If this object has been created
+   * due to a parse error, and you do not catch it (it gets thrown
+   * from the parser), then this method is called during the printing
+   * of the final stack trace, and hence the correct error message
+   * gets displayed.
+   */
+  public String getMessage() {
+    if (!specialConstructor) {
+      return super.getMessage();
+    }
+    String expected = "";
+    int maxSize = 0;
+    for (int i = 0; i < expectedTokenSequences.length; i++) {
+      if (maxSize < expectedTokenSequences[i].length) {
+        maxSize = expectedTokenSequences[i].length;
+      }
+      for (int j = 0; j < expectedTokenSequences[i].length; j++) {
+        expected += tokenImage[expectedTokenSequences[i][j]] + " ";
+      }
+      if (expectedTokenSequences[i][expectedTokenSequences[i].length - 1] != 0) {
+        expected += "...";
+      }
+      expected += eol + "    ";
+    }
+    String retval = "Encountered \"";
+    Token tok = currentToken.next;
+    for (int i = 0; i < maxSize; i++) {
+      if (i != 0) retval += " ";
+      if (tok.kind == 0) {
+        retval += tokenImage[0];
+        break;
+      }
+      retval += add_escapes(tok.image);
+      tok = tok.next; 
+    }
+    retval += "\" at line " + currentToken.next.beginLine + ", column " + currentToken.next.beginColumn;
+    retval += "." + eol;
+    if (expectedTokenSequences.length == 1) {
+      retval += "Was expecting:" + eol + "    ";
+    } else {
+      retval += "Was expecting one of:" + eol + "    ";
+    }
+    retval += expected;
+    return retval;
+  }
+
+  /**
+   * The end of line string for this machine.
+   */
+  protected String eol = System.getProperty("line.separator", "\n");
+ 
+  /**
+   * Used to convert raw characters to their escaped version
+   * when these raw version cannot be used as part of an ASCII
+   * string literal.
+   */
+  protected String add_escapes(String str) {
+      StringBuffer retval = new StringBuffer();
+      char ch;
+      for (int i = 0; i < str.length(); i++) {
+        switch (str.charAt(i))
+        {
+           case 0 :
+              continue;
+           case '\b':
+              retval.append("\\b");
+              continue;
+           case '\t':
+              retval.append("\\t");
+              continue;
+           case '\n':
+              retval.append("\\n");
+              continue;
+           case '\f':
+              retval.append("\\f");
+              continue;
+           case '\r':
+              retval.append("\\r");
+              continue;
+           case '\"':
+              retval.append("\\\"");
+              continue;
+           case '\'':
+              retval.append("\\\'");
+              continue;
+           case '\\':
+              retval.append("\\\\");
+              continue;
+           default:
+              if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) {
+                 String s = "0000" + Integer.toString(ch, 16);
+                 retval.append("\\u" + s.substring(s.length() - 4, s.length()));
+              } else {
+                 retval.append(ch);
+              }
+              continue;
+        }
+      }
+      return retval.toString();
+   }
+
+}
diff --git a/src/org/jdesktop/el/impl/parser/SimpleCharStream.java b/src/org/jdesktop/el/impl/parser/SimpleCharStream.java
new file mode 100644
index 0000000..f71552c
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/SimpleCharStream.java
@@ -0,0 +1,405 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JavaCC: Do not edit this line. SimpleCharStream.java Version 3.0 */
+package org.jdesktop.el.impl.parser;
+
+/**
+ * An implementation of interface CharStream, where the stream is assumed to
+ * contain only ASCII characters (without unicode processing).
+ */
+
+public class SimpleCharStream
+{
+  public static final boolean staticFlag = false;
+  int bufsize;
+  int available;
+  int tokenBegin;
+  public int bufpos = -1;
+  protected int bufline[];
+  protected int bufcolumn[];
+
+  protected int column = 0;
+  protected int line = 1;
+
+  protected boolean prevCharIsCR = false;
+  protected boolean prevCharIsLF = false;
+
+  protected java.io.Reader inputStream;
+
+  protected char[] buffer;
+  protected int maxNextCharInd = 0;
+  protected int inBuf = 0;
+
+  protected void ExpandBuff(boolean wrapAround)
+  {
+     char[] newbuffer = new char[bufsize + 2048];
+     int newbufline[] = new int[bufsize + 2048];
+     int newbufcolumn[] = new int[bufsize + 2048];
+
+     try
+     {
+        if (wrapAround)
+        {
+           System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
+           System.arraycopy(buffer, 0, newbuffer,
+                                             bufsize - tokenBegin, bufpos);
+           buffer = newbuffer;
+
+           System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
+           System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos);
+           bufline = newbufline;
+
+           System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
+           System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos);
+           bufcolumn = newbufcolumn;
+
+           maxNextCharInd = (bufpos += (bufsize - tokenBegin));
+        }
+        else
+        {
+           System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
+           buffer = newbuffer;
+
+           System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
+           bufline = newbufline;
+
+           System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
+           bufcolumn = newbufcolumn;
+
+           maxNextCharInd = (bufpos -= tokenBegin);
+        }
+     }
+     catch (Throwable t)
+     {
+        throw new Error(t.getMessage());
+     }
+
+
+     bufsize += 2048;
+     available = bufsize;
+     tokenBegin = 0;
+  }
+
+  protected void FillBuff() throws java.io.IOException
+  {
+     if (maxNextCharInd == available)
+     {
+        if (available == bufsize)
+        {
+           if (tokenBegin > 2048)
+           {
+              bufpos = maxNextCharInd = 0;
+              available = tokenBegin;
+           }
+           else if (tokenBegin < 0)
+              bufpos = maxNextCharInd = 0;
+           else
+              ExpandBuff(false);
+        }
+        else if (available > tokenBegin)
+           available = bufsize;
+        else if ((tokenBegin - available) < 2048)
+           ExpandBuff(true);
+        else
+           available = tokenBegin;
+     }
+
+     int i;
+     try {
+        if ((i = inputStream.read(buffer, maxNextCharInd,
+                                    available - maxNextCharInd)) == -1)
+        {
+           inputStream.close();
+           throw new java.io.IOException();
+        }
+        else
+           maxNextCharInd += i;
+        return;
+     }
+     catch(java.io.IOException e) {
+        --bufpos;
+        backup(0);
+        if (tokenBegin == -1)
+           tokenBegin = bufpos;
+        throw e;
+     }
+  }
+
+  public char BeginToken() throws java.io.IOException
+  {
+     tokenBegin = -1;
+     char c = readChar();
+     tokenBegin = bufpos;
+
+     return c;
+  }
+
+  protected void UpdateLineColumn(char c)
+  {
+     column++;
+
+     if (prevCharIsLF)
+     {
+        prevCharIsLF = false;
+        line += (column = 1);
+     }
+     else if (prevCharIsCR)
+     {
+        prevCharIsCR = false;
+        if (c == '\n')
+        {
+           prevCharIsLF = true;
+        }
+        else
+           line += (column = 1);
+     }
+
+     switch (c)
+     {
+        case '\r' :
+           prevCharIsCR = true;
+           break;
+        case '\n' :
+           prevCharIsLF = true;
+           break;
+        case '\t' :
+           column--;
+           column += (8 - (column & 07));
+           break;
+        default :
+           break;
+     }
+
+     bufline[bufpos] = line;
+     bufcolumn[bufpos] = column;
+  }
+
+  public char readChar() throws java.io.IOException
+  {
+     if (inBuf > 0)
+     {
+        --inBuf;
+
+        if (++bufpos == bufsize)
+           bufpos = 0;
+
+        return buffer[bufpos];
+     }
+
+     if (++bufpos >= maxNextCharInd)
+        FillBuff();
+
+     char c = buffer[bufpos];
+
+     UpdateLineColumn(c);
+     return (c);
+  }
+
+  /**
+   * @deprecated 
+   * @see #getEndColumn
+   */
+
+  public int getColumn() {
+     return bufcolumn[bufpos];
+  }
+
+  /**
+   * @deprecated 
+   * @see #getEndLine
+   */
+
+  public int getLine() {
+     return bufline[bufpos];
+  }
+
+  public int getEndColumn() {
+     return bufcolumn[bufpos];
+  }
+
+  public int getEndLine() {
+     return bufline[bufpos];
+  }
+
+  public int getBeginColumn() {
+     return bufcolumn[tokenBegin];
+  }
+
+  public int getBeginLine() {
+     return bufline[tokenBegin];
+  }
+
+  public void backup(int amount) {
+
+    inBuf += amount;
+    if ((bufpos -= amount) < 0)
+       bufpos += bufsize;
+  }
+
+  public SimpleCharStream(java.io.Reader dstream, int startline,
+  int startcolumn, int buffersize)
+  {
+    inputStream = dstream;
+    line = startline;
+    column = startcolumn - 1;
+
+    available = bufsize = buffersize;
+    buffer = new char[buffersize];
+    bufline = new int[buffersize];
+    bufcolumn = new int[buffersize];
+  }
+
+  public SimpleCharStream(java.io.Reader dstream, int startline,
+                                                           int startcolumn)
+  {
+     this(dstream, startline, startcolumn, 4096);
+  }
+
+  public SimpleCharStream(java.io.Reader dstream)
+  {
+     this(dstream, 1, 1, 4096);
+  }
+  public void ReInit(java.io.Reader dstream, int startline,
+  int startcolumn, int buffersize)
+  {
+    inputStream = dstream;
+    line = startline;
+    column = startcolumn - 1;
+
+    if (buffer == null || buffersize != buffer.length)
+    {
+      available = bufsize = buffersize;
+      buffer = new char[buffersize];
+      bufline = new int[buffersize];
+      bufcolumn = new int[buffersize];
+    }
+    prevCharIsLF = prevCharIsCR = false;
+    tokenBegin = inBuf = maxNextCharInd = 0;
+    bufpos = -1;
+  }
+
+  public void ReInit(java.io.Reader dstream, int startline,
+                                                           int startcolumn)
+  {
+     ReInit(dstream, startline, startcolumn, 4096);
+  }
+
+  public void ReInit(java.io.Reader dstream)
+  {
+     ReInit(dstream, 1, 1, 4096);
+  }
+  public SimpleCharStream(java.io.InputStream dstream, int startline,
+  int startcolumn, int buffersize)
+  {
+     this(new java.io.InputStreamReader(dstream), startline, startcolumn, 4096);
+  }
+
+  public SimpleCharStream(java.io.InputStream dstream, int startline,
+                                                           int startcolumn)
+  {
+     this(dstream, startline, startcolumn, 4096);
+  }
+
+  public SimpleCharStream(java.io.InputStream dstream)
+  {
+     this(dstream, 1, 1, 4096);
+  }
+
+  public void ReInit(java.io.InputStream dstream, int startline,
+                          int startcolumn, int buffersize)
+  {
+     ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, 4096);
+  }
+
+  public void ReInit(java.io.InputStream dstream)
+  {
+     ReInit(dstream, 1, 1, 4096);
+  }
+  public void ReInit(java.io.InputStream dstream, int startline,
+                                                           int startcolumn)
+  {
+     ReInit(dstream, startline, startcolumn, 4096);
+  }
+  public String GetImage()
+  {
+     if (bufpos >= tokenBegin)
+        return new String(buffer, tokenBegin, bufpos - tokenBegin + 1);
+     else
+        return new String(buffer, tokenBegin, bufsize - tokenBegin) +
+                              new String(buffer, 0, bufpos + 1);
+  }
+
+  public char[] GetSuffix(int len)
+  {
+     char[] ret = new char[len];
+
+     if ((bufpos + 1) >= len)
+        System.arraycopy(buffer, bufpos - len + 1, ret, 0, len);
+     else
+     {
+        System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0,
+                                                          len - bufpos - 1);
+        System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1);
+     }
+
+     return ret;
+  }
+
+  public void Done()
+  {
+     buffer = null;
+     bufline = null;
+     bufcolumn = null;
+  }
+
+  /**
+   * Method to adjust line and column numbers for the start of a token.
+   */
+  public void adjustBeginLineColumn(int newLine, int newCol)
+  {
+     int start = tokenBegin;
+     int len;
+
+     if (bufpos >= tokenBegin)
+     {
+        len = bufpos - tokenBegin + inBuf + 1;
+     }
+     else
+     {
+        len = bufsize - tokenBegin + bufpos + 1 + inBuf;
+     }
+
+     int i = 0, j = 0, k = 0;
+     int nextColDiff = 0, columnDiff = 0;
+
+     while (i < len &&
+            bufline[j = start % bufsize] == bufline[k = ++start % bufsize])
+     {
+        bufline[j] = newLine;
+        nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j];
+        bufcolumn[j] = newCol + columnDiff;
+        columnDiff = nextColDiff;
+        i++;
+     } 
+
+     if (i < len)
+     {
+        bufline[j] = newLine++;
+        bufcolumn[j] = newCol + columnDiff;
+
+        while (i++ < len)
+        {
+           if (bufline[j = start % bufsize] != bufline[++start % bufsize])
+              bufline[j] = newLine++;
+           else
+              bufline[j] = newLine;
+        }
+     }
+
+     line = bufline[j];
+     column = bufcolumn[j];
+  }
+
+}
diff --git a/src/org/jdesktop/el/impl/parser/SimpleNode.java b/src/org/jdesktop/el/impl/parser/SimpleNode.java
new file mode 100644
index 0000000..f0319f2
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/SimpleNode.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JJTree: Do not edit this line. SimpleNode.java */
+
+package org.jdesktop.el.impl.parser;
+
+import org.jdesktop.el.ELException;
+import org.jdesktop.el.MethodInfo;
+import org.jdesktop.el.PropertyNotWritableException;
+
+import org.jdesktop.el.impl.lang.ELSupport;
+import org.jdesktop.el.impl.lang.EvaluationContext;
+import org.jdesktop.el.impl.util.MessageFactory;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public abstract class SimpleNode extends ELSupport implements Node {
+    protected Node parent;
+
+    protected Node[] children;
+
+    protected int id;
+
+    protected String image;
+
+    public SimpleNode(int i) {
+        id = i;
+    }
+
+    public void jjtOpen() {
+    }
+
+    public void jjtClose() {
+    }
+
+    public void jjtSetParent(Node n) {
+        parent = n;
+    }
+
+    public Node jjtGetParent() {
+        return parent;
+    }
+
+    public void jjtAddChild(Node n, int i) {
+        if (children == null) {
+            children = new Node[i + 1];
+        } else if (i >= children.length) {
+            Node c[] = new Node[i + 1];
+            System.arraycopy(children, 0, c, 0, children.length);
+            children = c;
+        }
+        children[i] = n;
+    }
+
+    public Node jjtGetChild(int i) {
+        return children[i];
+    }
+
+    public int jjtGetNumChildren() {
+        return (children == null) ? 0 : children.length;
+    }
+
+    /*
+     * You can override these two methods in subclasses of SimpleNode to
+     * customize the way the node 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.
+     */
+
+    public String toString() {
+        if (this.image != null) {
+            return ELParserTreeConstants.jjtNodeName[id] + "[" + this.image
+                    + "]";
+        }
+        return ELParserTreeConstants.jjtNodeName[id];
+    }
+
+    public String toString(String prefix) {
+        return prefix + toString();
+    }
+
+    /*
+     * Override this method if you want to customize how the node 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 = (SimpleNode) children[i];
+                if (n != null) {
+                    n.dump(prefix + " ");
+                }
+            }
+        }
+    }
+
+    public String getImage() {
+        return image;
+    }
+
+    public void setImage(String image) {
+        this.image = image;
+    }
+
+    public Class getType(EvaluationContext ctx)
+            throws ELException {
+        throw new UnsupportedOperationException();
+    }
+
+    public Object getValue(EvaluationContext ctx)
+            throws ELException {
+        throw new UnsupportedOperationException();
+    }
+
+    public boolean isReadOnly(EvaluationContext ctx)
+            throws ELException {
+        return true;
+    }
+
+    public void setValue(EvaluationContext ctx, Object value)
+            throws ELException {
+        throw new PropertyNotWritableException(MessageFactory.get("error.syntax.set"));
+    }
+
+    public void accept(NodeVisitor visitor) throws ELException {
+        visitor.visit(this);
+        if (this.children != null && this.children.length > 0) {
+            for (int i = 0; i < this.children.length; i++) {
+                this.children[i].accept(visitor);
+            }
+        }
+    }
+
+    public Object invoke(EvaluationContext ctx, Class[] paramTypes, Object[] paramValues) throws ELException {
+        throw new UnsupportedOperationException();
+    }
+
+    public MethodInfo getMethodInfo(EvaluationContext ctx, Class[] paramTypes) throws ELException {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/src/org/jdesktop/el/impl/parser/Token.java b/src/org/jdesktop/el/impl/parser/Token.java
new file mode 100644
index 0000000..8c09ce9
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/Token.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JavaCC: Do not edit this line. Token.java Version 3.0 */
+package org.jdesktop.el.impl.parser;
+
+/**
+ * Describes the input token stream.
+ */
+
+public class Token {
+
+  /**
+   * An integer that describes the kind of this token.  This numbering
+   * system is determined by JavaCCParser, and a table of these numbers is
+   * stored in the file ...Constants.java.
+   */
+  public int kind;
+
+  /**
+   * beginLine and beginColumn describe the position of the first character
+   * of this token; endLine and endColumn describe the position of the
+   * last character of this token.
+   */
+  public int beginLine, beginColumn, endLine, endColumn;
+
+  /**
+   * The string image of the token.
+   */
+  public String image;
+
+  /**
+   * A reference to the next regular (non-special) token from the input
+   * stream.  If this is the last token from the input stream, or if the
+   * token manager has not read tokens beyond this one, this field is
+   * set to null.  This is true only if this token is also a regular
+   * token.  Otherwise, see below for a description of the contents of
+   * this field.
+   */
+  public Token next;
+
+  /**
+   * This field is used to access special tokens that occur prior to this
+   * token, but after the immediately preceding regular (non-special) token.
+   * If there are no such special tokens, this field is set to null.
+   * When there are more than one such special token, this field refers
+   * to the last of these special tokens, which in turn refers to the next
+   * previous special token through its specialToken field, and so on
+   * until the first special token (whose specialToken field is null).
+   * The next fields of special tokens refer to other special tokens that
+   * immediately follow it (without an intervening regular token).  If there
+   * is no such token, this field is null.
+   */
+  public Token specialToken;
+
+  /**
+   * Returns the image.
+   */
+  public String toString()
+  {
+     return image;
+  }
+
+  /**
+   * Returns a new Token object, by default. However, if you want, you
+   * can create and return subclass objects based on the value of ofKind.
+   * Simply add the cases to the switch for all those special cases.
+   * For example, if you have a subclass of Token called IDToken that
+   * you want to create if ofKind is ID, simlpy add something like :
+   *
+   *    case MyParserConstants.ID : return new IDToken();
+   *
+   * to the following switch statement. Then you can cast matchedToken
+   * variable to the appropriate type and use it in your lexical actions.
+   */
+  public static final Token newToken(int ofKind)
+  {
+     switch(ofKind)
+     {
+       default : return new Token();
+     }
+  }
+
+}
diff --git a/src/org/jdesktop/el/impl/parser/TokenMgrError.java b/src/org/jdesktop/el/impl/parser/TokenMgrError.java
new file mode 100644
index 0000000..54b038d
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/TokenMgrError.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ *
+ *//* Generated By:JavaCC: Do not edit this line. TokenMgrError.java Version 3.0 */
+package org.jdesktop.el.impl.parser;
+
+public class TokenMgrError extends Error
+{
+   /*
+    * Ordinals for various reasons why an Error of this type can be thrown.
+    */
+
+   /**
+    * Lexical error occured.
+    */
+   static final int LEXICAL_ERROR = 0;
+
+   /**
+    * An attempt wass made to create a second instance of a static token manager.
+    */
+   static final int STATIC_LEXER_ERROR = 1;
+
+   /**
+    * Tried to change to an invalid lexical state.
+    */
+   static final int INVALID_LEXICAL_STATE = 2;
+
+   /**
+    * Detected (and bailed out of) an infinite loop in the token manager.
+    */
+   static final int LOOP_DETECTED = 3;
+
+   /**
+    * Indicates the reason why the exception is thrown. It will have
+    * one of the above 4 values.
+    */
+   int errorCode;
+
+   /**
+    * Replaces unprintable characters by their espaced (or unicode escaped)
+    * equivalents in the given string
+    */
+   protected static final String addEscapes(String str) {
+      StringBuffer retval = new StringBuffer();
+      char ch;
+      for (int i = 0; i < str.length(); i++) {
+        switch (str.charAt(i))
+        {
+           case 0 :
+              continue;
+           case '\b':
+              retval.append("\\b");
+              continue;
+           case '\t':
+              retval.append("\\t");
+              continue;
+           case '\n':
+              retval.append("\\n");
+              continue;
+           case '\f':
+              retval.append("\\f");
+              continue;
+           case '\r':
+              retval.append("\\r");
+              continue;
+           case '\"':
+              retval.append("\\\"");
+              continue;
+           case '\'':
+              retval.append("\\\'");
+              continue;
+           case '\\':
+              retval.append("\\\\");
+              continue;
+           default:
+              if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) {
+                 String s = "0000" + Integer.toString(ch, 16);
+                 retval.append("\\u" + s.substring(s.length() - 4, s.length()));
+              } else {
+                 retval.append(ch);
+              }
+              continue;
+        }
+      }
+      return retval.toString();
+   }
+
+   /**
+    * Returns a detailed message for the Error when it is thrown by the
+    * token manager to indicate a lexical error.
+    * Parameters : 
+    *    EOFSeen     : indicates if EOF caused the lexicl error
+    *    curLexState : lexical state in which this error occured
+    *    errorLine   : line number when the error occured
+    *    errorColumn : column number when the error occured
+    *    errorAfter  : prefix that was seen before this error occured
+    *    curchar     : the offending character
+    * Note: You can customize the lexical error message by modifying this method.
+    */
+   protected static String LexicalError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar) {
+      return("Lexical error at line " +
+           errorLine + ", column " +
+           errorColumn + ".  Encountered: " +
+           (EOFSeen ? "<EOF> " : ("\"" + addEscapes(String.valueOf(curChar)) + "\"") + " (" + (int)curChar + "), ") +
+           "after : \"" + addEscapes(errorAfter) + "\"");
+   }
+
+   /**
+    * You can also modify the body of this method to customize your error messages.
+    * For example, cases like LOOP_DETECTED and INVALID_LEXICAL_STATE are not
+    * of end-users concern, so you can return something like : 
+    *
+    *     "Internal Error : Please file a bug report .... "
+    *
+    * from this method for such cases in the release version of your parser.
+    */
+   public String getMessage() {
+      return super.getMessage();
+   }
+
+   /*
+    * Constructors of various flavors follow.
+    */
+
+   public TokenMgrError() {
+   }
+
+   public TokenMgrError(String message, int reason) {
+      super(message);
+      errorCode = reason;
+   }
+
+   public TokenMgrError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar, int reason) {
+      this(LexicalError(EOFSeen, lexState, errorLine, errorColumn, errorAfter, curChar), reason);
+   }
+}
diff --git a/src/org/jdesktop/el/impl/parser/package.html b/src/org/jdesktop/el/impl/parser/package.html
new file mode 100644
index 0000000..6ae8488
--- /dev/null
+++ b/src/org/jdesktop/el/impl/parser/package.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<!--
+ Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ subject to license terms.
+-->
+
+<html>
+    <head>
+        <title></title>
+    </head>
+    <body>
+        <p>
+            Customized version of EL for Beans Binding; not for general use.
+            It is expected that we'll sync up with an official version of EL
+            in the future.
+        </p>
+    </body>
+</html>
diff --git a/src/org/jdesktop/el/impl/util/MessageFactory.java b/src/org/jdesktop/el/impl/util/MessageFactory.java
new file mode 100644
index 0000000..a421500
--- /dev/null
+++ b/src/org/jdesktop/el/impl/util/MessageFactory.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el.impl.util;
+
+import java.text.MessageFormat;
+import java.util.ResourceBundle;
+
+/**
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public final class MessageFactory {
+
+    protected final static ResourceBundle bundle = ResourceBundle
+            .getBundle("org.jdesktop.el.impl.Messages");
+    /**
+     * 
+     */
+    public MessageFactory() {
+        super();
+    }
+    
+    public static String get(final String key) {
+        return bundle.getString(key);
+    }
+
+    public static String get(final String key, final Object obj0) {
+        return getArray(key, new Object[] { obj0 });
+    }
+
+    public static String get(final String key, final Object obj0,
+            final Object obj1) {
+        return getArray(key, new Object[] { obj0, obj1 });
+    }
+
+    public static String get(final String key, final Object obj0,
+            final Object obj1, final Object obj2) {
+        return getArray(key, new Object[] { obj0, obj1, obj2 });
+    }
+
+    public static String get(final String key, final Object obj0,
+            final Object obj1, final Object obj2, final Object obj3) {
+        return getArray(key, new Object[] { obj0, obj1, obj2, obj3 });
+    }
+
+    public static String get(final String key, final Object obj0,
+            final Object obj1, final Object obj2, final Object obj3,
+            final Object obj4) {
+        return getArray(key, new Object[] { obj0, obj1, obj2, obj3, obj4 });
+    }
+
+    public static String getArray(final String key, final Object[] objA) {
+        return MessageFormat.format(bundle.getString(key), objA);
+    }
+
+}
diff --git a/src/org/jdesktop/el/impl/util/ReflectionUtil.java b/src/org/jdesktop/el/impl/util/ReflectionUtil.java
new file mode 100644
index 0000000..0ce2f0a
--- /dev/null
+++ b/src/org/jdesktop/el/impl/util/ReflectionUtil.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.el.impl.util;
+
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+
+import org.jdesktop.el.ELException;
+import org.jdesktop.el.MethodNotFoundException;
+import org.jdesktop.el.PropertyNotFoundException;
+
+import org.jdesktop.el.impl.lang.ELSupport;
+
+/**
+ * Utilities for Managing Serialization and Reflection
+ * 
+ * @author Jacob Hookom [jacob at hookom.net]
+ * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: kchung $
+ */
+public class ReflectionUtil {
+
+    protected static final String[] EMPTY_STRING = new String[0];
+
+    protected static final String[] PRIMITIVE_NAMES = new String[] { "boolean",
+            "byte", "char", "double", "float", "int", "long", "short", "void" };
+
+    protected static final Class[] PRIMITIVES = new Class[] { boolean.class,
+            byte.class, char.class, double.class, float.class, int.class,
+            long.class, short.class, Void.TYPE };
+
+    /**
+     * 
+     */
+    private ReflectionUtil() {
+        super();
+    }
+
+    public static Class forName(String name) throws ClassNotFoundException {
+        if (null == name || "".equals(name)) {
+            return null;
+        }
+        Class c = forNamePrimitive(name);
+        if (c == null) {
+            if (name.endsWith("[]")) {
+                String nc = name.substring(0, name.length() - 2);
+                c = Class.forName(nc, true, Thread.currentThread().getContextClassLoader());
+                c = Array.newInstance(c, 0).getClass();
+            } else {
+                c = Class.forName(name, true, Thread.currentThread().getContextClassLoader());
+            }
+        }
+        return c;
+    }
+
+    protected static Class forNamePrimitive(String name) {
+        if (name.length() <= 8) {
+            int p = Arrays.binarySearch(PRIMITIVE_NAMES, name);
+            if (p >= 0) {
+                return PRIMITIVES[p];
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Converts an array of Class names to Class types
+     * @param s
+     * @return
+     * @throws ClassNotFoundException
+     */
+    public static Class[] toTypeArray(String[] s) throws ClassNotFoundException {
+        if (s == null)
+            return null;
+        Class[] c = new Class[s.length];
+        for (int i = 0; i < s.length; i++) {
+            c[i] = forName(s[i]);
+        }
+        return c;
+    }
+
+    /**
+     * Converts an array of Class types to Class names
+     * @param c
+     * @return
+     */
+    public static String[] toTypeNameArray(Class[] c) {
+        if (c == null)
+            return null;
+        String[] s = new String[c.length];
+        for (int i = 0; i < c.length; i++) {
+            s[i] = c[i].getName();
+        }
+        return s;
+    }
+
+    /**
+     * Returns a method based on the criteria
+     * @param base the object that owns the method
+     * @param property the name of the method
+     * @param paramTypes the parameter types to use
+     * @return the method specified
+     * @throws MethodNotFoundException
+     */
+    public static Method getMethod(Object base, Object property,
+            Class[] paramTypes) throws MethodNotFoundException {
+        if (base == null || property == null) {
+            throw new MethodNotFoundException(MessageFactory.get(
+                    "error.method.notfound", base, property,
+                    paramString(paramTypes)));
+        }
+
+        String methodName = property.toString();
+
+        Method method = getMethod(base.getClass(), methodName, paramTypes);
+        if (method == null) {
+            throw new MethodNotFoundException(MessageFactory.get(
+                    "error.method.notfound", base, property,
+                    paramString(paramTypes)));
+        }
+        return method;
+    }
+
+    /*
+     * Get a public method form a public class or interface of a given method.
+     * Note that if the base is an instance of a non-public class that
+     * implements a public interface,  calling Class.getMethod() with the base
+     * will not find the method.  To correct this, a version of the
+     * same method must be found in a superclass or interface.
+     **/
+
+    static private Method getMethod(Class cl, String methodName,
+                                    Class[] paramTypes) {
+
+        Method m = null;
+        try {
+            m = cl.getMethod(methodName, paramTypes);
+        } catch (NoSuchMethodException ex) {
+            return null;
+        }
+
+        Class dclass  = m.getDeclaringClass();
+        if (Modifier.isPublic(dclass.getModifiers())) {
+            return m;
+        }
+
+        for (Class c: dclass.getInterfaces()) {
+            m = getMethod(c, methodName, paramTypes);
+            if (m != null) {
+                return m;
+            }
+        }
+        Class c = dclass.getSuperclass();
+        if (c != null) {
+            m = getMethod(c, methodName, paramTypes);
+            if (m != null) {
+                return m;
+            }
+        }
+        return null;
+    }
+
+    protected static final String paramString(Class[] types) {
+        if (types != null) {
+            StringBuffer sb = new StringBuffer();
+            for (int i = 0; i < types.length; i++) {
+                sb.append(types[i].getName()).append(", ");
+            }
+            if (sb.length() > 2) {
+                sb.setLength(sb.length() - 2);
+            }
+            return sb.toString();
+        }
+        return null;
+    }
+
+    /**
+     * @param base
+     * @param property
+     * @return
+     * @throws ELException
+     * @throws PropertyNotFoundException
+     */
+    public static PropertyDescriptor getPropertyDescriptor(Object base,
+            Object property) throws ELException, PropertyNotFoundException {
+        String name = ELSupport.coerceToString(property);
+        PropertyDescriptor p = null;
+        try {
+            PropertyDescriptor[] desc = Introspector.getBeanInfo(
+                    base.getClass()).getPropertyDescriptors();
+            for (int i = 0; i < desc.length; i++) {
+                if (desc[i].getName().equals(name)) {
+                    return desc[i];
+                }
+            }
+        } catch (IntrospectionException ie) {
+            throw new ELException(ie);
+        }
+        throw new PropertyNotFoundException(MessageFactory.get(
+                "error.property.notfound", base, name));
+    }
+}
diff --git a/src/org/jdesktop/el/impl/util/package.html b/src/org/jdesktop/el/impl/util/package.html
new file mode 100644
index 0000000..6ae8488
--- /dev/null
+++ b/src/org/jdesktop/el/impl/util/package.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<!--
+ Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ subject to license terms.
+-->
+
+<html>
+    <head>
+        <title></title>
+    </head>
+    <body>
+        <p>
+            Customized version of EL for Beans Binding; not for general use.
+            It is expected that we'll sync up with an official version of EL
+            in the future.
+        </p>
+    </body>
+</html>
diff --git a/src/org/jdesktop/el/package.html b/src/org/jdesktop/el/package.html
new file mode 100644
index 0000000..ee760d0
--- /dev/null
+++ b/src/org/jdesktop/el/package.html
@@ -0,0 +1,192 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<!--
+ Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ subject to license terms.
+-->
+</head>
+
+<body bgcolor="white">
+    
+Customized version of EL for Beans Binding; not for general use.
+It is expected that we'll sync up with an official version of EL
+in the future.
+
+<p>
+Provides the API for the <strong>Unified Expression Language</strong> shared by
+the JSP 2.1 and JSF 1.2 technologies.
+
+<p>The Expression Language (EL) is a simple language designed to satisfy
+the specific needs of web application developers. It is currently defined
+in its own specification document within the JavaServer Pages (tm) (JSP) 
+2.1 specification, but does not have any dependencies on any portion 
+of the JSP 2.1 specification. It is intended for general use outside of
+the JSP and JSF specifications as well.</p>
+
+<p>This package contains the classes and interfaces that describe
+and define the programmatic access to the Expression Language engine. The API
+is logically partitioned as follows:
+
+<ul>
+  <li><a href="#Context">EL Context</a></li>
+  <li><a href="#ExpressionObjects">Expression Objects</a></li>
+  <li><a href="#ExpressionCreation">Creation of Expressions</a></li>
+  <li><a href="#Resolver">Resolution of Model Objects and their Properties</a></li>
+  <li><a href="#Functions">EL Functions</a></li>
+  <li><a href="#Variables">EL Variables</a></li>
+</ul>
+
+<h3><a name="Context">EL Context</a></h3>
+
+<p>An important goal of the EL is to ensure it can be used in 
+a variety of environments. It must therefore provide enough flexibility
+to adapt to the specific requirements of the environment where it is
+being used.</p>
+
+<p>Class {@link javax.el.ELContext} is what links 
+the EL with the specific environment where it is being used.
+It provides
+the mechanism through which all relevant context for creating or
+evaluating an expression is specified.
+</p>
+
+<p>Creation of <code>ELContext</code> objects is controlled through 
+ the underlying technology. For example, in JSP, the
+ <code>JspContext.getELContext()</code> factory method is used.</p>
+ 
+ <p>Some technologies provide the ability to add an {@link javax.el.ELContextListener}
+ so that applications and frameworks can ensure their own context objects
+ are attached to any newly created <code>ELContext</code>.</p>
+ 
+<h3><a name="ExpressionObjects">Expression Objects</a></h3>
+
+<p>At the core of the Expression Language is the notion of an <i>expression</i>
+that gets parsed according to the grammar defined by the Expression Language.</p>
+
+<p>There are two types of expressions defined by the EL: <i>value expressions</i>
+and <i>method expressions</i>. A {@link javax.el.ValueExpression} such as 
+<code>"${customer.name}"</code> can be used either
+as an <i>rvalue</i> (return the value associated with property <code>name</code>
+of the model object <code>customer</code>) or as an <i>lvalue</i> 
+(set the value of the property <code>name</code> of the model object
+<code>customer</code>).</p>
+
+<p>A {@link javax.el.MethodExpression} such as 
+<code>"${handler.process}"</code> makes it possible to invoke a method 
+(<code>process</code>) on a specific model object (<code>handler</code>).</p>
+
+<p>All expression classes extend the base class {@link javax.el.Expression}, making them
+serializable and forcing them to implement <code>equals()</code> and 
+<code>hashCode()</code>. Morevover, each method on these expression classes 
+that actually evaluates an expression receives a parameter
+of class {@link javax.el.ELContext},
+which provides the context required to evaluate the expression.</p>
+
+
+<h3><a name="ExpressionCreation">Creation of Expressions</a></h3>
+
+<p>An expression is created through the {@link javax.el.ExpressionFactory} class.
+The factory provides two creation methods; one for each type of expression
+ supported by the EL.</p>
+
+<p>To create an expression, one must provide an {@link javax.el.ELContext}, 
+a string representing 
+the expression, and the expected type (<code>ValueExpression</code>) or signature 
+(<code>MethodExpression</code>). 
+
+The <code>ELContext</code> provides the context necessary to parse an expression.
+Specifically, if the expression uses an EL function 
+(for example <code>${fn:toUpperCase(customer.name)}</code>) or an
+EL variable, then  
+{@link javax.el.FunctionMapper} and {@link javax.el.VariableMapper}
+objects must be available within the <code>ELContext</code> so that EL functions and
+EL variables are properly mapped.
+</p>
+
+
+<h3><a name="Resolver">Resolution of Model Objects and their Properties</a></h3>
+
+<p>Through the {@link javax.el.ELResolver} base class, the EL 
+features a pluggable mechanism 
+to resolve model object references as well as properties of these objects.</p>
+
+<p>The EL API provides implementations of <code>ELResolver</code> supporting 
+property resolution for common data types which include
+arrays ({@link javax.el.ArrayELResolver}), JavaBeans ({@link javax.el.BeanELResolver}), <code>List</code>s ({@link javax.el.ListELResolver}), 
+<code>Map</code>s ({@link javax.el.MapELResolver}), and <code>ResourceBundle</code>s ({@link javax.el.ResourceBundleELResolver}).</p>
+
+<p>Tools can easily obtain more information about resolvable model objects and their 
+resolvable properties by calling
+method <code>getFeatureDescriptors</code> on the <code>ELResolver</code>. This method exposes objects
+of type <code>java.beans.FeatureDescriptor</code>, providing all information of interest 
+on top-level model objects as well as their properties.</p> 
+
+<h3><a name="Functions">EL Functions</a></h3>
+
+<p>If an EL expression uses a function 
+(for example <code>${fn:toUpperCase(customer.name)}</code>), then a 
+{@link javax.el.FunctionMapper} 
+object must also be specified within the <code>ELContext</code>. 
+The <code>FunctionMapper</code> is responsible to map
+ <code>${prefix:name()}</code> style functions to 
+static methods that can execute the specified functions. 
+</p>
+
+<h3><a name="Variables">EL Variables</a></h3>
+
+<p>Just like {@link javax.el.FunctionMapper} provides
+a flexible mechanism to add functions to the EL, {@link javax.el.VariableMapper}
+provides a flexible mechanism to support the notion of 
+<strong>EL variables</strong>.
+</p>
+ 
+<p>
+An EL variable does not directly refer to a model object that can then
+be resolved by an <code>ELResolver</code>. Instead, it refers to an EL
+expression. The evaluation of that EL expression gives the EL variable
+its value.
+</p>
+
+<p>
+For example, in the following code snippet
+<blockquote>
+  <code>&lt;h:inputText value="#{handler.customer.name}"/></code>
+</blockquote>
+
+<code>handler</code> refers to a model object that can be resolved by an EL Resolver.
+</p>
+<p>
+However, in this other example:
+<blockquote>
+<pre>
+&lt;c:forEach var="item" items="#{model.list}">
+   &lt;h:inputText value="#{item.name}"/>
+&lt;/c:forEach>
+</pre>
+</blockquote>
+
+<code>item</code> is an EL variable because it does not refer directly to a model
+object.  Instead, it refers to another EL expression, namely a
+specific item in the collection referred to by the EL expression
+<code>#{model.list}<code>.
+</p>
+
+<p>
+Assuming that there are three elements in <code>${model.list}</code>, this means
+that for
+each invocation of <code>&lt;h:inputText></code>, the following information 
+about <code>item</code> must be preserved in the {@link javax.el.VariableMapper}:
+<blockquote>
+    first invocation: <code>item</code> maps to first element in <code>${model.list}</code><br>
+    second invocation: <code>item</code> maps to second element in <code>${model.list}</code><br>
+    third invocation: <code>item</code> maps to third element in <code>${model.list}</code><br>
+</blockquote>
+<p>
+<code>VariableMapper</code> provides the mechanisms required to allow the mapping
+of an EL variable to the EL expression from which it gets its value.
+</p>
+
+</body>
+</html>
+
diff --git a/src/org/jdesktop/observablecollections/ObservableCollections.java b/src/org/jdesktop/observablecollections/ObservableCollections.java
new file mode 100644
index 0000000..c89338f
--- /dev/null
+++ b/src/org/jdesktop/observablecollections/ObservableCollections.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.observablecollections;
+
+import java.util.AbstractList;
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * {@code ObservableCollections} provides factory methods for creating
+ * observable lists and maps.
+ * 
+ * 
+ * @author sky
+ */
+public final class ObservableCollections {
+    /**
+     * Creates and returns an {@code ObservableMap} wrapping the supplied
+     * {@code Map}.
+     *
+     * @param map the {@code Map} to wrap
+     * @return an {@code ObservableMap}
+     * @throws IllegalArgumentException if {@code map} is {@code null}
+     */
+    public static <K,V> ObservableMap<K,V> observableMap(Map<K,V> map) {
+        if (map == null) {
+            throw new IllegalArgumentException("Map must be non-null");
+        }
+        return new ObservableMapImpl<K,V>(map);
+    }
+
+    /**
+     * Creates and returns an {@code ObservableList} wrapping the supplied
+     * {@code List}.
+     *
+     * @param list the {@code List} to wrap
+     * @return an {@code ObservableList}
+     * @throws IllegalArgumentException if {@code list} is {@code null}
+     */
+    public static <E> ObservableList<E> observableList(List<E> list) {
+        if (list == null) {
+            throw new IllegalArgumentException("List must be non-null");
+        }
+        return new ObservableListImpl<E>(list, false);
+    }
+
+    /**
+     * Creates and returns an {@code ObservableListHelper} wrapping
+     * the supplied {@code List}. If you can track changes to the underlying
+     * list, use this method instead of {@code observableList()}.
+     *
+     * @param list the {@code List} to wrap
+     * @return an {@code ObservableList}
+     * @throws IllegalArgumentException if {@code list} is {@code null}
+     *
+     * @see #observableList
+     */
+    public static <E> ObservableListHelper<E> observableListHelper(List<E> list) {
+        ObservableListImpl<E> oList = new ObservableListImpl<E>(list, true);
+        return new ObservableListHelper<E>(oList);
+    }
+    
+
+    /**
+     * {@code ObservableListHelper} is created by {@code observableListHelper},
+     * and useful when changes to individual elements of the list can be
+     * tracked.
+     *
+     * @see #observableListHelper
+     */
+    public static final class ObservableListHelper<E> {
+        private final ObservableListImpl<E> list;
+
+        ObservableListHelper(ObservableListImpl<E> list) {
+            this.list = list;
+        }
+
+        /**
+         * Returns the {@code ObservableList}.
+         *
+         * @return the observable list
+         */
+        public ObservableList<E> getObservableList() {
+            return list;
+        }
+
+        /**
+         * Sends notification that the element at the specified index
+         * has changed.
+         *
+         * @param index the index of the element that has changed
+         * @throws ArrayIndexOutOfBoundsException if index is outside the
+         *         range of the {@code List} ({@code < 0 || >= size})
+         */
+        public void fireElementChanged(int index) {
+            if (index < 0 || index >= list.size()) {
+                throw new ArrayIndexOutOfBoundsException("Illegal index");
+            }
+            list.fireElementChanged(index);
+        }
+    }
+
+    private static final class ObservableMapImpl<K,V> extends AbstractMap<K,V> 
+            implements ObservableMap<K,V> {
+        private Map<K,V> map;
+        private List<ObservableMapListener> listeners;
+        private Set<Map.Entry<K,V>> entrySet;
+        
+        ObservableMapImpl(Map<K,V> map) {
+            this.map = map;
+            listeners = new CopyOnWriteArrayList<ObservableMapListener>();
+        }
+        
+        public void clear() {
+            // Remove all elements via iterator to trigger notification
+            Iterator<K> iterator = keySet().iterator();
+            while (iterator.hasNext()) {
+                iterator.next();
+                iterator.remove();
+            }
+        }
+
+        public boolean containsKey(Object key) {
+            return map.containsKey(key);
+        }
+
+        public boolean containsValue(Object value) {
+            return map.containsValue(value);
+        }
+
+        public Set<Map.Entry<K,V>> entrySet() {
+            Set<Map.Entry<K,V>> es = entrySet;
+            return es != null ? es : (entrySet = new EntrySet());
+        }
+        
+        public V get(Object key) {
+            return map.get(key);
+        }
+
+        public boolean isEmpty() {
+            return map.isEmpty();
+        }
+        
+        public V put(K key, V value) {
+            V lastValue;
+            if (containsKey(key)) {
+                lastValue = map.put(key, value);
+                for (ObservableMapListener listener : listeners) {
+                    listener.mapKeyValueChanged(this, key, lastValue);
+                }
+            } else {
+                lastValue = map.put(key, value);
+                for (ObservableMapListener listener : listeners) {
+                    listener.mapKeyAdded(this, key);
+                }
+            }
+            return lastValue;
+        }
+        
+        public void putAll(Map<? extends K, ? extends V> m) {
+            for (K key : m.keySet()) {
+                put(key, m.get(key));
+            }
+        }
+        
+        public V remove(Object key) {
+            if (containsKey(key)) {
+                V value = map.remove(key);
+                for (ObservableMapListener listener : listeners) {
+                    listener.mapKeyRemoved(this, key, value);
+                }
+                return value;
+            }
+            return null;
+        }
+        
+        public int size() {
+            return map.size();
+        }
+        
+        public void addObservableMapListener(ObservableMapListener listener) {
+            listeners.add(listener);
+        }
+
+        public void removeObservableMapListener(ObservableMapListener listener) {
+            listeners.remove(listener);
+        }
+        
+        
+        private class EntryIterator implements Iterator<Map.Entry<K,V>> {
+            private Iterator<Map.Entry<K,V>> realIterator;
+            private Map.Entry<K,V> last;
+            
+            EntryIterator() {
+                realIterator = map.entrySet().iterator();
+            }
+            public boolean hasNext() {
+                return realIterator.hasNext();
+            }
+
+            public Map.Entry<K,V> next() {
+                last = realIterator.next();
+                return last;
+            }
+
+            public void remove() {
+                if (last == null) {
+                    throw new IllegalStateException();
+                }
+                Object toRemove = last.getKey();
+                last = null;
+                ObservableMapImpl.this.remove(toRemove);
+            }
+        }
+
+        
+        private class EntrySet extends AbstractSet<Map.Entry<K,V>> {
+            public Iterator<Map.Entry<K,V>> iterator() {
+                return new EntryIterator();
+            }
+            @SuppressWarnings("unchecked")
+            public boolean contains(Object o) {
+                if (!(o instanceof Map.Entry)) {
+                    return false;
+                }
+                Map.Entry<K,V> e = (Map.Entry<K,V>)o;
+                return containsKey(e.getKey());
+            }
+
+            @SuppressWarnings("unchecked")
+            public boolean remove(Object o) {
+                if (o instanceof Map.Entry) {
+                    K key = ((Map.Entry<K,V>)o).getKey();
+                    if (containsKey(key)) {
+                        remove(key);
+                        return true;
+                    }
+                }
+                return false;
+            }
+            
+            public int size() {
+                return ObservableMapImpl.this.size();
+            }
+            public void clear() {
+                ObservableMapImpl.this.clear();
+            }
+        }
+    }
+    
+
+    private static final class ObservableListImpl<E> extends AbstractList<E>
+            implements ObservableList<E> {
+        private final boolean supportsElementPropertyChanged;
+        private List<E> list;
+        private List<ObservableListListener> listeners;
+        
+        ObservableListImpl(List<E> list, boolean supportsElementPropertyChanged) {
+            this.list = list;
+            listeners = new CopyOnWriteArrayList<ObservableListListener>();
+            this.supportsElementPropertyChanged = supportsElementPropertyChanged;
+        }
+
+        public E get(int index) {
+            return list.get(index);
+        }
+
+        public int size() {
+            return list.size();
+        }
+
+        public E set(int index, E element) {
+            E oldValue = list.set(index, element);
+            for (ObservableListListener listener : listeners) {
+                listener.listElementReplaced(this, index, oldValue);
+            }
+            return oldValue;
+        }
+
+        public void add(int index, E element) {
+            list.add(index, element);
+            modCount++;
+            for (ObservableListListener listener : listeners) {
+                listener.listElementsAdded(this, index, 1);
+            }
+        }
+
+        public E remove(int index) {
+            E oldValue = list.remove(index);
+            modCount++;
+            for (ObservableListListener listener : listeners) {
+                listener.listElementsRemoved(this, index,
+                        java.util.Collections.singletonList(oldValue));
+            }
+            return oldValue;
+        }
+
+        public boolean addAll(Collection<? extends E> c) {
+            return addAll(size(), c);
+        }
+        
+        public boolean addAll(int index, Collection<? extends E> c) {
+            if (list.addAll(index, c)) {
+                modCount++;
+                for (ObservableListListener listener : listeners) {
+                    listener.listElementsAdded(this, index, c.size());
+                }
+            }
+            return false;
+        }
+
+        public void clear() {
+            List<E> dup = new ArrayList<E>(list);
+            list.clear();
+            modCount++;
+            if (dup.size() != 0) {
+                for (ObservableListListener listener : listeners) {
+                    listener.listElementsRemoved(this, 0, dup);
+                }
+            }
+        }
+
+        public boolean containsAll(Collection<?> c) {
+            return list.containsAll(c);
+        }
+
+        public <T> T[] toArray(T[] a) {
+            return list.toArray(a);
+        }
+
+        public Object[] toArray() {
+            return list.toArray();
+        }
+
+        private void fireElementChanged(int index) {
+            for (ObservableListListener listener : listeners) {
+                listener.listElementPropertyChanged(this, index);
+            }
+        }
+
+        public void addObservableListListener(ObservableListListener listener) {
+            listeners.add(listener);
+        }
+
+        public void removeObservableListListener(ObservableListListener listener) {
+            listeners.remove(listener);
+        }
+
+        public boolean supportsElementPropertyChanged() {
+            return supportsElementPropertyChanged;
+        }
+    }
+}
diff --git a/src/org/jdesktop/observablecollections/ObservableList.java b/src/org/jdesktop/observablecollections/ObservableList.java
new file mode 100644
index 0000000..5250a58
--- /dev/null
+++ b/src/org/jdesktop/observablecollections/ObservableList.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.observablecollections;
+
+import java.util.List;
+
+/**
+ * A {@code List} that notifies listeners of changes.
+ *
+ * @author sky
+ */
+public interface ObservableList<E> extends List<E> {
+    /**
+     * Adds a listener that is notified when the list changes.
+     *
+     * @param listener the listener to add
+     */
+    public void addObservableListListener(ObservableListListener listener);
+
+    /**
+     * Removes a listener.
+     *
+     * @param listener the listener to remove
+     */
+    public void removeObservableListListener(ObservableListListener listener);
+
+    /**
+     * Returns {@code true} if this list sends out notification when
+     * the properties of an element change. This method may be used
+     * to determine if a listener needs to be installed on each of
+     * the elements of the list.
+     *
+     * @return {@code true} if this list sends out notification when
+     *         the properties of an element change
+     */
+    public boolean supportsElementPropertyChanged();
+}
diff --git a/src/org/jdesktop/observablecollections/ObservableListListener.java b/src/org/jdesktop/observablecollections/ObservableListListener.java
new file mode 100644
index 0000000..85c05e5
--- /dev/null
+++ b/src/org/jdesktop/observablecollections/ObservableListListener.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.observablecollections;
+
+import java.util.EventListener;
+import java.util.List;
+
+/**
+ * Notification types from an {@code ObservableList}.
+ *
+ * @author sky
+ */
+public interface ObservableListListener extends EventListener {
+    /**
+     * Notification that elements have been added to the list.
+     *
+     * @param list the {@code ObservableList} that has changed
+     * @param index the index the elements were added to
+     * @param length the number of elements that were added
+     */
+    public void listElementsAdded(ObservableList list, int index, int length);
+
+    /**
+     * Notification that elements have been removed from the list.
+     *
+     * @param list the {@code ObservableList} that has changed
+     * @param index the starting index the elements were removed from
+     * @param oldElements a list containing the elements that were removed.
+     */
+    public void listElementsRemoved(ObservableList list, int index,
+                                    List oldElements);
+
+    /**
+     * Notification that an element has been replaced by another in the list.
+     *
+     * @param list the {@code ObservableList} that has changed
+     * @param index the index of the element that was replaced
+     * @param oldElement the element at the index before the change
+     */
+    public void listElementReplaced(ObservableList list, int index,
+                                    Object oldElement);
+
+    /**
+     * Notification than a property of an element in this list has changed.
+     * Not all {@code ObservableLists} support this notification. Only
+     * observable lists that return {@code true} from
+     * {@code supportsElementPropertyChanged} send this notification.
+     *
+     * @param list the {@code ObservableList} that has changed
+     * @param index the index of the element that changed
+     */
+    public void listElementPropertyChanged(ObservableList list, int index);
+}
diff --git a/src/org/jdesktop/observablecollections/ObservableMap.java b/src/org/jdesktop/observablecollections/ObservableMap.java
new file mode 100644
index 0000000..70caaae
--- /dev/null
+++ b/src/org/jdesktop/observablecollections/ObservableMap.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.observablecollections;
+
+import java.util.Map;
+
+/**
+ * A {@code Map} that notifies listeners of changes to the {@code Map}.
+ *
+ * @author sky
+ */
+public interface ObservableMap<K,V> extends Map<K,V> {
+    /**
+     * Adds a listener to this observable map.
+     *
+     * @param listener the listener to add
+     */
+    public void addObservableMapListener(ObservableMapListener listener);
+
+    /**
+     * Removes a listener from this observable map.
+     *
+     * @param listener the listener to remove
+     */
+    public void removeObservableMapListener(ObservableMapListener listener);
+}
diff --git a/src/org/jdesktop/observablecollections/ObservableMapListener.java b/src/org/jdesktop/observablecollections/ObservableMapListener.java
new file mode 100644
index 0000000..752a00c
--- /dev/null
+++ b/src/org/jdesktop/observablecollections/ObservableMapListener.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.observablecollections;
+
+/**
+ * Notification types from an {@code ObservableMap}.
+ *
+ * @author sky
+ */
+public interface ObservableMapListener {
+    /**
+     * Notification that the value of an existing key has changed.
+     *
+     * @param map the {@code ObservableMap} that changed
+     * @param key the key
+     * @param lastValue the previous value
+     */
+    public void mapKeyValueChanged(ObservableMap map, Object key,
+                                   Object lastValue);
+
+    /**
+     * Notification that a key has been added.
+     *
+     * @param map the {@code ObservableMap} that changed
+     * @param key the key
+     */
+    public void mapKeyAdded(ObservableMap map, Object key);
+
+    /**
+     * Notification that a key has been removed
+     *
+     * @param map the {@code ObservableMap} that changed
+     * @param key the key
+     * @param value value for key before key was removed
+     */
+    public void mapKeyRemoved(ObservableMap map, Object key, Object value);
+
+    // PENDING: should we special case clear?
+}
diff --git a/src/org/jdesktop/observablecollections/package.html b/src/org/jdesktop/observablecollections/package.html
new file mode 100644
index 0000000..a6cddbc
--- /dev/null
+++ b/src/org/jdesktop/observablecollections/package.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<!--
+ Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ subject to license terms.
+-->
+
+<html>
+    <head>
+        <title></title>
+    </head>
+    <body>
+        <p>
+            Provides support for observing changes to collection classes.
+            The classes in this package are not part of JSR 295.
+        </p>
+    </body>
+</html>
diff --git a/src/org/jdesktop/swingbinding/ElementsProperty.java b/src/org/jdesktop/swingbinding/ElementsProperty.java
new file mode 100644
index 0000000..254905e
--- /dev/null
+++ b/src/org/jdesktop/swingbinding/ElementsProperty.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.swingbinding;
+
+import java.util.*;
+import org.jdesktop.beansbinding.Property;
+import org.jdesktop.beansbinding.PropertyHelper;
+import org.jdesktop.beansbinding.PropertyStateEvent;
+
+/**
+ * @author Shannon Hickey
+ */
+class ElementsProperty<TS> extends PropertyHelper<TS, List> {
+
+    class ElementsPropertyStateEvent extends PropertyStateEvent {
+        private boolean ignore;
+
+        public ElementsPropertyStateEvent(Property sourceProperty,
+                                          Object sourceObject,
+                                          boolean valueChanged,
+                                          Object oldValue,
+                                          Object newValue,
+                                          boolean writeableChanged,
+                                          boolean isWriteable) {
+            this(sourceProperty,
+                 sourceObject,
+                 valueChanged,
+                 oldValue,
+                 newValue,
+                 writeableChanged,
+                 isWriteable,
+                 false);
+        }
+
+        public ElementsPropertyStateEvent(Property sourceProperty,
+                                          Object sourceObject,
+                                          boolean valueChanged,
+                                          Object oldValue,
+                                          Object newValue,
+                                          boolean writeableChanged,
+                                          boolean isWriteable,
+                                          boolean ignore) {
+            super(sourceProperty,
+                 sourceObject,
+                 valueChanged,
+                 oldValue,
+                 newValue,
+                 writeableChanged,
+                 isWriteable);
+            
+            this.ignore = ignore;
+        }
+        
+        boolean shouldIgnore() {
+            return ignore;
+        }
+    }
+
+    private boolean accessible;
+    private List list;
+
+    ElementsProperty() {
+        super(true);
+    }
+
+    public Class<List> getWriteType(TS source) {
+        if (!accessible) {
+            throw new UnsupportedOperationException("Unwriteable");
+        }
+
+        return (Class<List>)List.class;
+    }
+
+    public List getValue(TS source) {
+        if (!accessible) {
+            throw new UnsupportedOperationException("Unreadable");
+        }
+
+        return list;
+    }
+
+    private void setValue0(TS source, List list, boolean ignore) {
+        if (!accessible) {
+            throw new UnsupportedOperationException("Unwriteable");
+        }
+
+        if (this.list == list) {
+            return;
+        }
+
+        List old = this.list;
+        this.list = list;
+
+        PropertyStateEvent pse = new ElementsPropertyStateEvent(this, null, true, old, list, false, true, ignore);
+        firePropertyStateChange(pse);
+    }
+
+    
+    public void setValue(TS source, List list) {
+        setValue0(source, list, false);
+    }
+
+    void setValueAndIgnore(TS source, List list) {
+        setValue0(source, list, true);
+    }
+
+    public boolean isReadable(TS source) {
+        return accessible;
+    }
+
+    public boolean isWriteable(TS source) {
+        return accessible;
+    }
+
+    public String toString() {
+        return "elements";
+    }
+
+    void setAccessible(boolean accessible) {
+        if (this.accessible == accessible) {
+            return;
+        }
+
+        this.accessible = accessible;
+
+        PropertyStateEvent pse;
+
+        if (accessible) {
+            pse = new ElementsPropertyStateEvent(this, null, true, PropertyStateEvent.UNREADABLE, null, true, true, true);
+        } else {
+            Object old = list;
+            list = null;
+            pse = new ElementsPropertyStateEvent(this, null, true, old, PropertyStateEvent.UNREADABLE, true, false, true);
+        }
+
+        firePropertyStateChange(pse);
+    }
+
+    boolean isAccessible() {
+        return accessible;
+    }
+
+}
diff --git a/src/org/jdesktop/swingbinding/JComboBoxBinding.java b/src/org/jdesktop/swingbinding/JComboBoxBinding.java
new file mode 100644
index 0000000..79b9a49
--- /dev/null
+++ b/src/org/jdesktop/swingbinding/JComboBoxBinding.java
@@ -0,0 +1,393 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.swingbinding;
+
+import javax.swing.*;
+import javax.swing.event.*;
+import java.util.*;
+import java.util.concurrent.CopyOnWriteArrayList;
+import org.jdesktop.beansbinding.AutoBinding;
+import org.jdesktop.beansbinding.Property;
+import org.jdesktop.beansbinding.PropertyStateEvent;
+import org.jdesktop.beansbinding.PropertyStateListener;
+import org.jdesktop.swingbinding.impl.AbstractColumnBinding;
+import org.jdesktop.swingbinding.impl.ListBindingManager;
+import static org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.*;
+
+/**
+ * Binds a {@code List} of objects to act as the items of a {@code JComboBox}.
+ * Each object in the source {@code List} is an item in the {@code JComboBox}.
+ * Instances of {@code JComboBoxBinding} are obtained by calling one of the
+ * {@code createJComboBoxBinding} methods in the {@code SwingBindings} class.
+ * <p>
+ * Here is an example of creating a binding from a {@code List} of {@code Country}
+ * objects to a {@code JComboBox}:
+ * <p>
+ * <pre><code>
+ *    // create the country list
+ *    List<Country> countries = createCountryList();
+ *
+ *    // create the binding from List to JComboBox
+ *    JComboBoxBinding cb = SwingBindings.createJComboBoxBinding(READ, countries, jComboBox);
+ *
+ *    // realize the binding
+ *    cb.bind();
+ * </code></pre>
+ * <p>
+ * If the {@code List} is an instance of {@code ObservableList}, then changes to
+ * the {@code List} contents (such as adding, removing or replacing an object)
+ * are reflected in the {@code JComboBox}. <b>Important:</b> Changing the contents
+ * of a non-observable {@code List} while it is participating in a
+ * {@code JComboBoxBinding} is unsupported, resulting in undefined behavior and
+ * possible exceptions.
+ * <p>
+ * <a name="CLARIFICATION">{@code JComboBoxBinding} requires</a>
+ * extra clarification on the operation of the
+ * {@code refresh} and {@code save} methods and the meaning of the update
+ * strategy. The target property of a {@code JComboBoxBinding} is not the
+ * target {@code JComboBox} property provided in the constructor, but rather a
+ * private synthetic property representing the {@code List} of objects to show
+ * in the target {@code JComboBox}. This synthetic property is readable/writeable
+ * only when the {@code JComboBoxBinding} is bound and the target {@code JComboBox}
+ * property is readable with a {@code non-null} value.
+ * <p>
+ * It is this private synthetic property on which the {@code refresh} and
+ * {@code save} methods operate; meaning that these methods simply cause syncing
+ * between the value of the source {@code List} property and the value of the
+ * synthetic target property (representing the {@code List} to be shown in the
+ * target {@code JComboBox}). These methods do not, therefore, have anything to do
+ * with refreshing <i>values</i> in the {@code JComboBox}. Likewise, the update
+ * strategy, which simply controls when {@code refresh} and {@code save} are
+ * automatically called, also has nothing to do with refreshing <i>values</i>
+ * in the {@code JComboBox}.
+ * <p>
+ * <b>Note:</b> At the current time, the {@code READ_WRITE} update strategy
+ * is not useful for {@code JComboBoxBinding}. To prevent unwanted confusion,
+ * {@code READ_WRITE} is translated to {@code READ} by {@code JComboBoxBinding's}
+ * constructor.
+ * <p>
+ * {@code JComboBoxBinding} works by installing a custom model on the target
+ * {@code JComboBox}, as appropriate, to represent the source {@code List}. The
+ * model is installed on a target {@code JComboBox} with the first succesful call
+ * to {@code refresh} with that {@code JComboBox} as the target. Subsequent calls
+ * to {@code refresh} update the elements in this already-installed model.
+ * The model is uninstalled from a target {@code JComboBox} when either the
+ * {@code JComboBoxBinding} is unbound or when the target {@code JComboBox} property
+ * changes to no longer represent that {@code JComboBox}. Note: When the model is
+ * uninstalled from a {@code JComboBox}, it is replaced with a {@code DefaultComboBoxModel},
+ * in order to leave the {@code JComboBox} functional.
+ * <p>
+ * Some of the above is easier to understand with an example. Let's consider
+ * a {@code JComboBoxBinding} ({@code binding}), with update strategy
+ * {@code READ}, between a property representing a {@code List} ({@code listP})
+ * and a property representing a {@code JComboBox} ({@code jComboBoxP}). {@code listP}
+ * and {@code jComboBoxP} both start off readable, referring to a {@code non-null}
+ * {@code List} and {@code non-null} {@code JComboBox} respectively. Let's look at
+ * what happens for each of a sequence of events:
+ * <p>
+ * <table border=1>
+ *   <tr><th>Sequence</th><th>Event</th><th>Result</th></tr>
+ *   <tr valign="baseline">
+ *     <td align="center">1</td>
+ *     <td>explicit call to {@code binding.bind()}</td>
+ *     <td>
+ *         - synthetic target property becomes readable/writeable
+ *         <br>
+ *         - {@code refresh()} is called
+ *         <br>
+ *         - model is installed on target {@code JComboBox}, representing list of objects
+ *     </td>
+ *   </tr>
+ *   <tr valign="baseline">
+ *     <td align="center">2</td>
+ *     <td>{@code listP} changes to a new {@code List}</td>
+ *     <td>
+ *         - {@code refresh()} is called
+ *         <br>
+ *         - model is updated with new list of objects
+ *     </td>
+ *   </tr>
+ *   <tr valign="baseline">
+ *     <td align="center"><a name="STEP3" href="#NOTICE">3</a></td>
+ *     <td>{@code jComboBoxP} changes to a new {@code JComboBox}</td>
+ *     <td>
+ *         - model is uninstalled from old {@code JComboBox}
+ *     </td>
+ *   </tr>
+ *   <tr valign="baseline">
+ *     <td align="center">4</td>
+ *     <td>explicit call to {@code binding.refresh()}</td>
+ *     <td>
+ *         - model is installed on target {@code JComboBox}, representing list of objects
+ *     </td>
+ *   </tr>
+ *   <tr valign="baseline">
+ *     <td align="center">5</td>
+ *     <td>{@code listP} changes to a new {@code List}</td>
+ *     <td>
+ *         - {@code refresh()} is called
+ *         <br>
+ *         - model is updated with new list of objects
+ *     </td>
+ *   </tr>
+ *   <tr valign="baseline">
+ *     <td align="center">6</td>
+ *     <td>explicit call to {@code binding.unbind()}</td>
+ *     <td>
+ *         - model is uninstalled from target {@code JComboBox}
+ *     </td>
+ *   </tr>
+ * </table>
+ * <p>
+ * <a name="NOTICE">Notice</a> that in <a href="#STEP3">step 3</a>, when the value
+ * of the {@code JComboBox} property changed, the new {@code JComboBox} did not
+ * automatically get the model with the elements applied to it. A change to the
+ * target value should not cause an {@code AutoBinding} to sync the target from
+ * the source. Step 4 forces a sync by explicitly calling {@code refresh}.
+ * Alternatively, it could be caused by any other action that results
+ * in a {@code refresh} (for example, the source property changing value, or an
+ * explicit call to {@code unbind} followed by {@code bind}).
+ * <p>
+ * In addition to binding the items of a {@code JComboBox}, it is possible to
+ * bind to the selected item of a {@code JComboBox}.
+ * See the list of <a href="package-summary.html#SWING_PROPS">
+ * interesting swing properties</a> in the package summary for more details.
+ *
+ * @param <E> the type of elements in the source {@code List}
+ * @param <SS> the type of source object (on which the source property resolves to {@code List})
+ * @param <TS> the type of target object (on which the target property resolves to {@code JComboBox})
+ *
+ * @author Shannon Hickey
+ */
+public final class JComboBoxBinding<E, SS, TS> extends AutoBinding<SS, List<E>, TS, List> {
+
+    private Property<TS, ? extends JComboBox> comboP;
+    private ElementsProperty<TS> elementsP;
+    private Handler handler = new Handler();
+    private JComboBox combo;
+    private BindingComboBoxModel model;
+
+    /**
+     * Constructs an instance of {@code JComboBoxBinding}.
+     *
+     * @param strategy the update strategy
+     * @param sourceObject the source object
+     * @param sourceListProperty a property on the source object that resolves to the {@code List} of elements
+     * @param targetObject the target object
+     * @param targetJComboBoxProperty a property on the target object that resolves to a {@code JComboBox}
+     * @param name a name for the {@code JComboBoxBinding}
+     * @throws IllegalArgumentException if the source property or target property is {@code null}
+     */
+    protected JComboBoxBinding(UpdateStrategy strategy, SS sourceObject, Property<SS, List<E>> sourceListProperty, TS targetObject, Property<TS, ? extends JComboBox> targetJComboBoxProperty, String name) {
+        super(strategy == READ_WRITE ? READ : strategy,
+              sourceObject, sourceListProperty, targetObject, new ElementsProperty<TS>(), name);
+
+        if (targetJComboBoxProperty == null) {
+            throw new IllegalArgumentException("target JComboBox property can't be null");
+        }
+
+        comboP = targetJComboBoxProperty;
+        elementsP = (ElementsProperty<TS>)getTargetProperty();
+    }
+
+    protected void bindImpl() {
+        elementsP.setAccessible(isComboAccessible());
+        comboP.addPropertyStateListener(getTargetObject(), handler);
+        elementsP.addPropertyStateListener(null, handler);
+        super.bindImpl();
+    }
+
+    protected void unbindImpl() {
+        elementsP.removePropertyStateListener(null, handler);
+        comboP.removePropertyStateListener(getTargetObject(), handler);
+        elementsP.setAccessible(false);
+        cleanupForLast();
+        super.unbindImpl();
+    }
+
+    private boolean isComboAccessible() {
+        return comboP.isReadable(getTargetObject()) && comboP.getValue(getTargetObject()) != null;
+    }
+
+    private boolean isComboAccessible(Object value) {
+        return value != null && value != PropertyStateEvent.UNREADABLE;
+    }
+
+    private void cleanupForLast() {
+        if (combo == null) {
+            return;
+        }
+
+        combo.setSelectedItem(null);
+        combo.setModel(new DefaultComboBoxModel());
+        model.updateElements(null, combo.isEditable());
+        combo = null;
+        model = null;
+    }
+
+    private class Handler implements PropertyStateListener {
+        public void propertyStateChanged(PropertyStateEvent pse) {
+            if (!pse.getValueChanged()) {
+                return;
+            }
+
+            if (pse.getSourceProperty() == comboP) {
+                cleanupForLast();
+                
+                boolean wasAccessible = isComboAccessible(pse.getOldValue());
+                boolean isAccessible = isComboAccessible(pse.getNewValue());
+
+                if (wasAccessible != isAccessible) {
+                    elementsP.setAccessible(isAccessible);
+                } else if (elementsP.isAccessible()) {
+                    elementsP.setValueAndIgnore(null, null);
+                }
+            } else {
+                if (((ElementsProperty.ElementsPropertyStateEvent)pse).shouldIgnore()) {
+                    return;
+                }
+
+                if (combo == null) {
+                    combo = comboP.getValue(getTargetObject());
+                    combo.setSelectedItem(null);
+                    model = new BindingComboBoxModel();
+                    combo.setModel(model);
+                }
+
+                model.updateElements((List)pse.getNewValue(), combo.isEditable());
+            }
+        }
+    }
+
+    private final class BindingComboBoxModel extends ListBindingManager implements ComboBoxModel  {
+        private final List<ListDataListener> listeners;
+        private Object selectedItem = null;
+        private int selectedModelIndex = -1;
+
+        public BindingComboBoxModel() {
+            listeners = new CopyOnWriteArrayList<ListDataListener>();
+        }
+
+        public void updateElements(List<?> elements, boolean isEditable) {
+            setElements(elements, false);
+
+            if (!isEditable || selectedModelIndex != -1) {
+                selectedItem = null;
+                selectedModelIndex = -1;
+            }
+            
+            if (size() <= 0) {
+                if (selectedModelIndex != -1) {
+                    selectedModelIndex = -1;
+                    selectedItem = null;
+                }
+            } else {
+                if (selectedItem == null) {
+                    selectedModelIndex = 0;
+                    selectedItem = getElementAt(selectedModelIndex);
+                }
+            }
+
+            allChanged();
+        }
+
+        protected AbstractColumnBinding[] getColBindings() {
+            return new AbstractColumnBinding[0];
+        }
+
+        public Object getSelectedItem() {
+            return selectedItem;
+        }
+
+        public void setSelectedItem(Object item) {
+            // This is what DefaultComboBoxModel does (yes, yuck!)
+            if ((selectedItem != null && !selectedItem.equals(item)) || selectedItem == null && item != null) {
+                selectedItem = item;
+                contentsChanged(-1, -1);
+                selectedModelIndex = -1;
+                if (item != null) {
+                    int size = size();
+                    for (int i = 0; i < size; i++) {
+                        if (item.equals(getElementAt(i))) {
+                            selectedModelIndex = i;
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        protected void allChanged() {
+            contentsChanged(0, size());
+        }
+
+        protected void valueChanged(int row, int column) {
+            // we're not expecting any value changes since we don't have any
+            // detail bindings for JComboBox
+        }
+
+        protected void added(int index, int length) {
+            assert length > 0; // enforced by ListBindingManager
+
+            ListDataEvent e = new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, index, index + length - 1);
+            int size = listeners.size();
+            for (int i = size - 1; i >= 0; i--) {
+                listeners.get(i).intervalAdded(e);
+            }
+
+            if (size() == length && selectedItem == null) {
+                setSelectedItem(getElementAt(0));
+            }
+        }
+
+        protected void removed(int index, int length) {
+            assert length > 0; // enforced by ListBindingManager
+
+            ListDataEvent e = new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, index, index + length - 1);
+            int size = listeners.size();
+            for (int i = size - 1; i >= 0; i--) {
+                listeners.get(i).intervalRemoved(e);
+            }
+            
+            if (selectedModelIndex >= index && selectedModelIndex < index + length) {
+                if (size() == 0) {
+                    setSelectedItem(null);
+                } else {
+                    setSelectedItem(getElementAt(Math.max(index - 1, 0)));
+                }
+            }
+        }
+
+        protected void changed(int row) {
+            contentsChanged(row, row);
+        }
+
+        private void contentsChanged(int row0, int row1) {
+            ListDataEvent e = new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, row0, row1);
+            int size = listeners.size();
+            for (int i = size - 1; i >= 0; i--) {
+                listeners.get(i).contentsChanged(e);
+            }
+        }
+        
+        public Object getElementAt(int index) {
+            return getElement(index);
+        }
+        
+        public void addListDataListener(ListDataListener l) {
+            listeners.add(l);
+        }
+        
+        public void removeListDataListener(ListDataListener l) {
+            listeners.remove(l);
+        }
+        
+        public int getSize() {
+            return size();
+        }
+    }
+
+}
diff --git a/src/org/jdesktop/swingbinding/JListBinding.java b/src/org/jdesktop/swingbinding/JListBinding.java
new file mode 100644
index 0000000..b2b2bfd
--- /dev/null
+++ b/src/org/jdesktop/swingbinding/JListBinding.java
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.swingbinding;
+
+import javax.swing.*;
+import javax.swing.event.*;
+import java.util.*;
+import java.util.concurrent.CopyOnWriteArrayList;
+import org.jdesktop.beansbinding.AutoBinding;
+import org.jdesktop.beansbinding.ObjectProperty;
+import org.jdesktop.beansbinding.Property;
+import org.jdesktop.beansbinding.PropertyStateEvent;
+import org.jdesktop.beansbinding.PropertyStateListener;
+import org.jdesktop.swingbinding.impl.AbstractColumnBinding;
+import org.jdesktop.swingbinding.impl.ListBindingManager;
+import static org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.*;
+
+/**
+ * Binds a {@code List} of objects to act as the elements of a {@code JList}.
+ * Each object in the source {@code List} provides one element in the {@code JList}.
+ * By setting a {@link org.jdesktop.swingbinding.JListBinding.DetailBinding DetailBinding}
+ * you can specify the property to use to derive each list element from its
+ * corresponding object in the source {@code List}. The default {@code DetailBinding} uses
+ * the objects directly. Instances of {@code JListBinding} are obtained by
+ * calling one of the {@code createJListBinding} methods in the {@code SwingBindings}
+ * class.
+ * <p>
+ * Here is an example of creating a binding from a {@code List} of {@code Person}
+ * objects to a {@code JList}:
+ * <p>
+ * <pre><code>
+ *    // create the person list
+ *    List<Person> people = createPersonList();
+ *
+ *    // create the binding from List to JList
+ *    JListBinding lb = SwingBindings.createJListBinding(READ, people, jList);
+ *
+ *    // define the property to be used to derive list elements
+ *    ELProperty fullNameP = ELProperty.create("${firstName} ${lastName}");
+ *
+ *    // add the detail binding
+ *    lb.setDetailBinding(fullNameP);
+ *
+ *    // realize the binding
+ *    lb.bind();
+ * </code></pre>
+ * <p>
+ * The {@code JList} target of a {@code JListBinding} acts as a live view of
+ * the objects in the source {@code List}, regardless of the update strategy (the
+ * meaning of the update strategy is <a href="#CLARIFICATION">clarified later</a>
+ * in this document). {@code JListBinding} listens to the property specified for
+ * any {@code DetailBinding}, for all objects in the {@code List}, and updates
+ * the values displayed in the {@code JList} in response to change. If the
+ * {@code List} is an instance of {@code ObservableList}, then changes to the
+ * {@code List} contents (such as adding, removing or replacing an object) are
+ * also reflected in the {@code JList}. <b>Important:</b> Changing the contents
+ * of a non-observable {@code List} while it is participating in a
+ * {@code JListBinding} is unsupported, resulting in undefined behavior and
+ * possible exceptions.
+ * <p>
+ * <a name="CLARIFICATION">{@code JListBinding} requires</a>
+ * extra clarification on the operation of the
+ * {@code refresh} and {@code save} methods and the meaning of the update
+ * strategy. The target property of a {@code JListBinding} is not the
+ * target {@code JList} property provided in the constructor, but rather a
+ * private synthetic property representing the {@code List} of objects to show
+ * in the target {@code JList}. This synthetic property is readable/writeable
+ * only when the {@code JListBinding} is bound and the target {@code JList}
+ * property is readable with a {@code non-null} value.
+ * <p>
+ * It is this private synthetic property on which the {@code refresh} and
+ * {@code save} methods operate; meaning that these methods simply cause syncing
+ * between the value of the source {@code List} property and the value of the
+ * synthetic target property (representing the {@code List} to be shown in the
+ * target {@code JList}). These methods do not, therefore, have anything to do
+ * with refreshing <i>values</i> in the {@code JList}. Likewise, the update
+ * strategy, which simply controls when {@code refresh} and {@code save} are
+ * automatically called, also has nothing to do with refreshing <i>values</i>
+ * in the {@code JList}.
+ * <p>
+ * <b>Note:</b> At the current time, the {@code READ_WRITE} update strategy
+ * is not useful for {@code JListBinding}. To prevent unwanted confusion,
+ * {@code READ_WRITE} is translated to {@code READ} by {@code JListBinding's}
+ * constructor.
+ * <p>
+ * {@code JListBinding} works by installing a custom model on the target
+ * {@code JList}, as appropriate, to represent the source {@code List}. The
+ * model is installed on a target {@code JList} with the first succesful call
+ * to {@code refresh} with that {@code JList} as the target. Subsequent calls
+ * to {@code refresh} update the elements in this already-installed model.
+ * The model is uninstalled from a target {@code JList} when either the
+ * {@code JListBinding} is unbound or when the target {@code JList} property
+ * changes to no longer represent that {@code JList}. Note: When the model is
+ * uninstalled from a {@code JList}, it is replaced with a {@code DefaultListModel},
+ * in order to leave the {@code JList} functional.
+ * <p>
+ * Some of the above is easier to understand with an example. Let's consider
+ * a {@code JListBinding} ({@code binding}), with update strategy
+ * {@code READ}, between a property representing a {@code List} ({@code listP})
+ * and a property representing a {@code JList} ({@code jListP}). {@code listP}
+ * and {@code jListP} both start off readable, referring to a {@code non-null}
+ * {@code List} and {@code non-null} {@code JList} respectively. Let's look at
+ * what happens for each of a sequence of events:
+ * <p>
+ * <table border=1>
+ *   <tr><th>Sequence</th><th>Event</th><th>Result</th></tr>
+ *   <tr valign="baseline">
+ *     <td align="center">1</td>
+ *     <td>explicit call to {@code binding.bind()}</td>
+ *     <td>
+ *         - synthetic target property becomes readable/writeable
+ *         <br>
+ *         - {@code refresh()} is called
+ *         <br>
+ *         - model is installed on target {@code JList}, representing list of objects
+ *     </td>
+ *   </tr>
+ *   <tr valign="baseline">
+ *     <td align="center">2</td>
+ *     <td>{@code listP} changes to a new {@code List}</td>
+ *     <td>
+ *         - {@code refresh()} is called
+ *         <br>
+ *         - model is updated with new list of objects
+ *     </td>
+ *   </tr>
+ *   <tr valign="baseline">
+ *     <td align="center"><a name="STEP3" href="#NOTICE">3</a></td>
+ *     <td>{@code jListP} changes to a new {@code JList}</td>
+ *     <td>
+ *         - model is uninstalled from old {@code JList}
+ *     </td>
+ *   </tr>
+ *   <tr valign="baseline">
+ *     <td align="center">4</td>
+ *     <td>explicit call to {@code binding.refresh()}</td>
+ *     <td>
+ *         - model is installed on target {@code JList}, representing list of objects
+ *     </td>
+ *   </tr>
+ *   <tr valign="baseline">
+ *     <td align="center">5</td>
+ *     <td>{@code listP} changes to a new {@code List}</td>
+ *     <td>
+ *         - {@code refresh()} is called
+ *         <br>
+ *         - model is updated with new list of objects
+ *     </td>
+ *   </tr>
+ *   <tr valign="baseline">
+ *     <td align="center">6</td>
+ *     <td>explicit call to {@code binding.unbind()}</td>
+ *     <td>
+ *         - model is uninstalled from target {@code JList}
+ *     </td>
+ *   </tr>
+ * </table>
+ * <p>
+ * <a name="NOTICE">Notice</a> that in <a href="#STEP3">step 3</a>, when the value
+ * of the {@code JList} property changed, the new {@code JList} did not
+ * automatically get the model with the elements applied to it. A change to the
+ * target value should not cause an {@code AutoBinding} to sync the target from
+ * the source. Step 4 forces a sync by explicitly calling {@code refresh}.
+ * Alternatively, it could be caused by any other action that results
+ * in a {@code refresh} (for example, the source property changing value, or an
+ * explicit call to {@code unbind} followed by {@code bind}).
+ * <p>
+ * {@code DetailBindings} are managed by the {@code JList}. They are not
+ * to be explicitly bound, unbound, added to a {@code BindingGroup}, or accessed
+ * in a way that is not allowed for a managed binding.
+ * <p>
+ * In addition to binding the elements of a {@code JList}, it is possible to
+ * bind to the selection of a {@code JList}. When binding to the selection of a {@code JList}
+ * backed by a {@code JListBinding}, the selection is always in terms of elements
+ * from the source {@code List}, regardless of any {@code DetailBinding} specified.
+ * See the list of <a href="package-summary.html#SWING_PROPS">
+ * interesting swing properties</a> in the package summary for more details.
+ *
+ * @param <E> the type of elements in the source {@code List}
+ * @param <SS> the type of source object (on which the source property resolves to {@code List})
+ * @param <TS> the type of target object (on which the target property resolves to {@code JList})
+ *
+ * @author Shannon Hickey
+ */
+public final class JListBinding<E, SS, TS> extends AutoBinding<SS, List<E>, TS, List> {
+
+    private Property<TS, ? extends JList> listP;
+    private ElementsProperty<TS> elementsP;
+    private Handler handler = new Handler();
+    private JList list;
+    private BindingListModel model;
+    private DetailBinding detailBinding;
+
+    /**
+     * Constructs an instance of {@code JListBinding}.
+     *
+     * @param strategy the update strategy
+     * @param sourceObject the source object
+     * @param sourceListProperty a property on the source object that resolves to the {@code List} of elements
+     * @param targetObject the target object
+     * @param targetJListProperty a property on the target object that resolves to a {@code JList}
+     * @param name a name for the {@code JListBinding}
+     * @throws IllegalArgumentException if the source property or target property is {@code null}
+     */
+    protected JListBinding(UpdateStrategy strategy, SS sourceObject, Property<SS, List<E>> sourceListProperty, TS targetObject, Property<TS, ? extends JList> targetJListProperty, String name) {
+        super(strategy == READ_WRITE ? READ : strategy,
+              sourceObject, sourceListProperty, targetObject, new ElementsProperty<TS>(), name);
+
+        if (targetJListProperty == null) {
+            throw new IllegalArgumentException("target JList property can't be null");
+        }
+
+        listP = targetJListProperty;
+        elementsP = (ElementsProperty<TS>)getTargetProperty();
+        setDetailBinding(null);
+    }
+
+    protected void bindImpl() {
+        elementsP.setAccessible(isListAccessible());
+        listP.addPropertyStateListener(getTargetObject(), handler);
+        elementsP.addPropertyStateListener(null, handler);
+        super.bindImpl();
+    }
+
+    protected void unbindImpl() {
+        elementsP.removePropertyStateListener(null, handler);
+        listP.removePropertyStateListener(getTargetObject(), handler);
+        elementsP.setAccessible(false);
+        cleanupForLast();
+        super.unbindImpl();
+    }
+
+    private boolean isListAccessible() {
+        return listP.isReadable(getTargetObject()) && listP.getValue(getTargetObject()) != null;
+    }
+
+    private boolean isListAccessible(Object value) {
+        return value != null && value != PropertyStateEvent.UNREADABLE;
+    }
+
+    private void cleanupForLast() {
+        if (list == null) {
+            return;
+        }
+
+        resetListSelection();
+        list.setModel(new DefaultListModel());
+        list = null;
+        model.setElements(null, true);
+        model = null;
+    }
+    
+    /**
+     * Creates a {@code DetailBinding} and sets it as the {@code DetailBinding}
+     * for this {@code JListBinding}. A {@code DetailBinding} specifies the property
+     * of the objects in the source {@code List} to be used as the elements of the
+     * {@code JList}. If the {@code detailProperty} parameter is {@code null}, the
+     * {@code DetailBinding} specifies that the objects themselves be used.
+     *
+     * @param detailProperty the property with which to derive each list value
+     *        from its corresponding object in the source {@code List}
+     * @return the {@code DetailBinding}
+     */
+    public DetailBinding setDetailBinding(Property<E, ?> detailProperty) {
+        return setDetailBinding(detailProperty, null);
+    }
+
+    /**
+     * Creates a named {@code DetailBinding} and sets it as the {@code DetailBinding}
+     * for this {@code JListBinding}. A {@code DetailBinding} specifies the property
+     * of the objects in the source {@code List} to be used as the elements of the
+     * {@code JList}. If the {@code detailProperty} parameter is {@code null}, the
+     * {@code DetailBinding} specifies that the objects themselves be used.
+     *
+     * @param detailProperty the property with which to derive each list value
+     *        from its corresponding object in the source {@code List}
+     * @return the {@code DetailBinding}
+     */
+    public DetailBinding setDetailBinding(Property<E, ?> detailProperty, String name) {
+        throwIfBound();
+
+        if (name == null && JListBinding.this.getName() != null) {
+            name = JListBinding.this.getName() + ".DETAIL_BINDING";
+        }
+
+        detailBinding = detailProperty == null ?
+                        new DetailBinding(ObjectProperty.<E>create(), name) :
+                        new DetailBinding(detailProperty, name);
+        return detailBinding;
+    }
+
+    /**
+     * Returns the {@code DetailBinding} for this {@code JListBinding}.
+     * A {@code DetailBinding} specifies the property of the source {@code List} elements
+     * to be used as the elements of the {@code JList}.
+     *
+     * @return the {@code DetailBinding}
+     * @see #setDetailBinding(Property, String)
+     */
+    public DetailBinding getDetailBinding() {
+        return detailBinding;
+    }
+
+    private final Property DETAIL_PROPERTY = new Property() {
+        public Class<Object> getWriteType(Object source) {
+            return Object.class;
+        }
+
+        public Object getValue(Object source) {
+            throw new UnsupportedOperationException();
+        }
+
+        public void setValue(Object source, Object value) {
+            throw new UnsupportedOperationException();
+        }
+
+        public boolean isReadable(Object source) {
+            throw new UnsupportedOperationException();
+        }
+
+        public boolean isWriteable(Object source) {
+            return true;
+        }
+
+        public void addPropertyStateListener(Object source, PropertyStateListener listener) {
+            throw new UnsupportedOperationException();
+        }
+
+        public void removePropertyStateListener(Object source, PropertyStateListener listener) {
+            throw new UnsupportedOperationException();
+        }
+
+        public PropertyStateListener[] getPropertyStateListeners(Object source) {
+            throw new UnsupportedOperationException();
+        }
+    };
+
+    /**
+     * {@code DetailBinding} represents a binding between a property of the elements
+     * in the {@code JListBinding's} source {@code List}, and the values shown in
+     * the {@code JList}. Values in the {@code JList} are aquired by fetching the
+     * value of the {@code DetailBinding's} source property for the associated object
+     * in the source {@code List}.
+     * <p>
+     * A {@code Converter} may be specified on a {@code DetailBinding}. Specifying a
+     * {@code Validator} is also possible, but doesn't make sense since {@code JList}
+     * values aren't editable.
+     * <p>
+     * {@code DetailBindings} are managed by their {@code JListBinding}. They are not
+     * to be explicitly bound, unbound, added to a {@code BindingGroup}, or accessed
+     * in a way that is not allowed for a managed binding.
+     *
+     * @see org.jdesktop.swingbinding.JListBinding#setDetailBinding(Property, String)
+     */
+    public final class DetailBinding extends AbstractColumnBinding {
+
+        private DetailBinding(Property<E, ?> detailProperty, String name) {
+            super(0, detailProperty, DETAIL_PROPERTY, name);
+        }
+
+    }
+
+    private class Handler implements PropertyStateListener {
+        public void propertyStateChanged(PropertyStateEvent pse) {
+            if (!pse.getValueChanged()) {
+                return;
+            }
+
+            if (pse.getSourceProperty() == listP) {
+                cleanupForLast();
+                
+                boolean wasAccessible = isListAccessible(pse.getOldValue());
+                boolean isAccessible = isListAccessible(pse.getNewValue());
+
+                if (wasAccessible != isAccessible) {
+                    elementsP.setAccessible(isAccessible);
+                } else if (elementsP.isAccessible()) {
+                    elementsP.setValueAndIgnore(null, null);
+                }
+            } else {
+                if (((ElementsProperty.ElementsPropertyStateEvent)pse).shouldIgnore()) {
+                    return;
+                }
+
+                if (list == null) {
+                    list = listP.getValue(getTargetObject());
+                    resetListSelection();
+                    model = new BindingListModel();
+                    list.setModel(model);
+                } else {
+                    resetListSelection();
+                }
+
+                model.setElements((List)pse.getNewValue(), true);
+            }
+        }
+    }
+
+    private void resetListSelection() {
+        ListSelectionModel selectionModel = list.getSelectionModel();
+        selectionModel.setValueIsAdjusting(true);
+        selectionModel.clearSelection();
+        selectionModel.setAnchorSelectionIndex(-1);
+        selectionModel.setLeadSelectionIndex(-1);
+        selectionModel.setValueIsAdjusting(false);
+    }
+    
+    private final class BindingListModel extends ListBindingManager implements ListModel  {
+        private final List<ListDataListener> listeners;
+
+        public BindingListModel() {
+            listeners = new CopyOnWriteArrayList<ListDataListener>();
+        }
+
+        protected AbstractColumnBinding[] getColBindings() {
+            return new AbstractColumnBinding[] {getDetailBinding()};
+        }
+
+        protected void allChanged() {
+            contentsChanged(0, size());
+        }
+
+        protected void valueChanged(int row, int column) {
+            contentsChanged(row, row);
+        }
+
+        protected void added(int index, int length) {
+            assert length > 0; // enforced by ListBindingManager
+
+            ListDataEvent e = new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, index, index + length - 1);
+            for (ListDataListener listener : listeners) {
+                listener.intervalAdded(e);
+            }
+        }
+
+        protected void removed(int index, int length) {
+            assert length > 0; // enforced by ListBindingManager
+
+            ListDataEvent e = new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, index, index + length - 1);
+            for (ListDataListener listener : listeners) {
+                listener.intervalRemoved(e);
+            }
+        }
+
+        protected void changed(int row) {
+            contentsChanged(row, row);
+        }
+
+        private void contentsChanged(int row0, int row1) {
+            ListDataEvent e = new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, row0, row1);
+            for (ListDataListener listener : listeners) {
+                listener.contentsChanged(e);
+            }
+        }
+
+        public Object getElementAt(int index) {
+            return valueAt(index, 0);
+        }
+
+        public void addListDataListener(ListDataListener l) {
+            listeners.add(l);
+        }
+
+        public void removeListDataListener(ListDataListener l) {
+            listeners.remove(l);
+        }
+
+        public int getSize() {
+            return size();
+        }
+    }
+}
diff --git a/src/org/jdesktop/swingbinding/JTableBinding.java b/src/org/jdesktop/swingbinding/JTableBinding.java
new file mode 100644
index 0000000..4de1525
--- /dev/null
+++ b/src/org/jdesktop/swingbinding/JTableBinding.java
@@ -0,0 +1,841 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.swingbinding;
+
+import javax.swing.*;
+import javax.swing.table.*;
+import javax.swing.event.*;
+import java.util.*;
+import java.util.concurrent.CopyOnWriteArrayList;
+import org.jdesktop.beansbinding.BindingListener;
+import org.jdesktop.beansbinding.Binding.*;
+import org.jdesktop.beansbinding.AutoBinding;
+import org.jdesktop.beansbinding.Property;
+import org.jdesktop.beansbinding.PropertyStateEvent;
+import org.jdesktop.beansbinding.PropertyStateListener;
+import org.jdesktop.swingbinding.impl.AbstractColumnBinding;
+import org.jdesktop.swingbinding.impl.ListBindingManager;
+import static org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.*;
+
+/**
+ * Binds a {@code List} of objects to act as the rows of a {@code JTable}.
+ * Each object in the source {@code List} represents one row in the {@code JTable}.
+ * Mappings from properties of the source objects to columns are created by
+ * adding {@link org.jdesktop.swingbinding.JTableBinding.ColumnBinding ColumnBindings}
+ * to a {@code JTableBinding}. Instances of {@code JTableBinding} are obtained by
+ * calling one of the {@code createJTableBinding} methods in the {@code SwingBindings}
+ * class.
+ * <p>
+ * Here is an example of creating a binding from a {@code List} of {@code Person}
+ * objects to a {@code JTable}:
+ * <p>
+ * <pre><code>
+ *    // create the person List
+ *    List<Person> people = createPersonList();
+ *
+ *    // create the binding from List to JTable
+ *    JTableBinding tb = SwingBindings.createJTableBinding(READ, people, jTable);
+ *
+ *    // define the properties to be used for the columns
+ *    BeanProperty firstNameP = BeanProperty.create("firstName");
+ *    BeanProperty lastNameP = BeanProperty.create("lastName");
+ *    BeanProperty ageP = BeanProperty.create("age");
+ *
+ *    // configure how the properties map to columns
+ *    tb.addColumnBinding(firstNameP).setColumnName("First Name");
+ *    tb.addColumnBinding(lastNameP).setColumnName("Last Name");
+ *    tb.addColumnBinding(ageP).setColumnName("Age").setColumnClass(Integer.class);
+ *
+ *    // realize the binding
+ *    tb.bind();
+ * </code></pre>
+ * <p>
+ * The {@code JTable} target of a {@code JTableBinding} acts as a live view of
+ * the objects in the source {@code List},
+ * regardless of the update strategy (the meaning of the update strategy is
+ * <a href="#CLARIFICATION">clarified later</a> in this document). {@code JTableBinding}
+ * listens to the properties specified for the {@code ColumnBindings}, 
+ * for all objects in the {@code List}, and updates the values
+ * displayed in the {@code JTable} in response to change. All successful
+ * edits made to {@code JTable} cell values are immediately committed back to
+ * corresponding objects in the source {@code List}. If the {@code List} is an
+ * instance of {@code ObservableList}, then changes to the {@code List} contents
+ * (such as adding, removing or replacing an object) are also reflected in the
+ * {@code JTable}. <b>Important:</b> Changing the contents of a non-observable
+ * {@code List} while it is participating in a {@code JTableBinding} is unsupported,
+ * resulting in undefined behavior and possible exceptions.
+ * <p>
+ * <a name="EDITABILITY">A cell</a> in the {@code JTable} is editable for any given row and
+ * column when all of the following are true: the property specified for that column
+ * by its {@code ColumnBinding} is writeable for the object representing that row,
+ * the {@code "editable"} property of the {@code JTableBinding} is {@code true}
+ * (the default), and the {@code "editable"} property of the {@code ColumnBinding}
+ * is {@code true} (the default).
+ * <p>
+ * <a name="CLARIFICATION">{@code JTableBinding} requires</a>
+ * extra clarification on the operation of the
+ * {@code refresh} and {@code save} methods and the meaning of the update
+ * strategy. The target property of a {@code JTableBinding} is not the
+ * target {@code JTable} property provided in the constructor, but rather a
+ * private synthetic property representing the {@code List} of objects to show
+ * in the target {@code JTable}. This synthetic property is readable/writeable
+ * only when the {@code JTableBinding} is bound and the target {@code JTable}
+ * property is readable with a {@code non-null} value.
+ * <p>
+ * It is this private synthetic property on which the {@code refresh} and
+ * {@code save} methods operate; meaning that these methods simply cause syncing
+ * between the value of the source {@code List} property and the value of the
+ * synthetic target property (representing the {@code List} to be shown in the
+ * target {@code JTable}). These methods do not, therefore, have anything to do
+ * with refreshing or saving <i>values</i> in the {@code JTable}. Likewise, the update
+ * strategy, which simply controls when {@code refresh} and {@code save} are
+ * automatically called, also has nothing to do with refreshing or saving
+ * <i>values</i> in the {@code JTable}.
+ * <p>
+ * <b>Note:</b> At the current time, the {@code READ_WRITE} update strategy
+ * is not useful for {@code JTableBinding}. To prevent unwanted confusion,
+ * {@code READ_WRITE} is translated to {@code READ} by {@code JTableBinding's}
+ * constructor.
+ * <p>
+ * {@code JTableBinding} works by installing a custom model on the target
+ * {@code JTable}, as appropriate, to represent the source {@code List}. The
+ * model is installed on a target {@code JTable} with the first succesful call
+ * to {@code refresh} with that {@code JTable} as the target. Subsequent calls
+ * to {@code refresh} update the elements in this already-installed model.
+ * The model is uninstalled from a target {@code JTable} when either the
+ * {@code JTableBinding} is unbound or when the target {@code JTable} property
+ * changes to no longer represent that {@code JTable}. Note: When the model is
+ * uninstalled from a {@code JTable}, it is replaced with a {@code DefaultTableModel},
+ * in order to leave the {@code JTable} functional.
+ * <p>
+ * Some of the above is easier to understand with an example. Let's consider
+ * a {@code JTableBinding} ({@code binding}), with update strategy
+ * {@code READ}, between a property representing a {@code List} ({@code listP})
+ * and a property representing a {@code JTable} ({@code jTableP}). {@code listP}
+ * and {@code jTableP} both start off readable, referring to a {@code non-null}
+ * {@code List} and {@code non-null} {@code JTable} respectively. Let's look at
+ * what happens for each of a sequence of events:
+ * <p>
+ * <table border=1>
+ *   <tr><th>Sequence</th><th>Event</th><th>Result</th></tr>
+ *   <tr valign="baseline">
+ *     <td align="center">1</td>
+ *     <td>explicit call to {@code binding.bind()}</td>
+ *     <td>
+ *         - synthetic target property becomes readable/writeable
+ *         <br>
+ *         - {@code refresh()} is called
+ *         <br>
+ *         - model is installed on target {@code JTable}, representing list of objects
+ *     </td>
+ *   </tr>
+ *   <tr valign="baseline">
+ *     <td align="center">2</td>
+ *     <td>{@code listP} changes to a new {@code List}</td>
+ *     <td>
+ *         - {@code refresh()} is called
+ *         <br>
+ *         - model is updated with new list of objects
+ *     </td>
+ *   </tr>
+ *   <tr valign="baseline">
+ *     <td align="center"><a name="STEP3" href="#NOTICE">3</a></td>
+ *     <td>{@code jTableP} changes to a new {@code JTable}</td>
+ *     <td>
+ *         - model is uninstalled from old {@code JTable}
+ *     </td>
+ *   </tr>
+ *   <tr valign="baseline">
+ *     <td align="center">4</td>
+ *     <td>explicit call to {@code binding.refresh()}</td>
+ *     <td>
+ *         - model is installed on target {@code JTable}, representing list of objects
+ *     </td>
+ *   </tr>
+ *   <tr valign="baseline">
+ *     <td align="center">5</td>
+ *     <td>{@code listP} changes to a new {@code List}</td>
+ *     <td>
+ *         - {@code refresh()} is called
+ *         <br>
+ *         - model is updated with new list of objects
+ *     </td>
+ *   </tr>
+ *   <tr valign="baseline">
+ *     <td align="center">6</td>
+ *     <td>explicit call to {@code binding.unbind()}</td>
+ *     <td>
+ *         - model is uninstalled from target {@code JTable}
+ *     </td>
+ *   </tr>
+ * </table>
+ * <p>
+ * <a name="NOTICE">Notice</a> that in <a href="#STEP3">step 3</a>, when the value
+ * of the {@code JTable} property changed, the new {@code JTable} did not
+ * automatically get the model with the elements applied to it. A change to the
+ * target value should not cause an {@code AutoBinding} to sync the target from
+ * the source. Step 4 forces a sync by explicitly calling {@code refresh}.
+ * Alternatively, it could be caused by any other action that results
+ * in a {@code refresh} (for example, the source property changing value, or an
+ * explicit call to {@code unbind} followed by {@code bind}).
+ * <p>
+ * {@code ColumnBindings} are managed by the {@code JTableBinding}. They are not
+ * to be explicitly bound, unbound, added to a {@code BindingGroup}, or accessed
+ * in a way that is not allowed for a managed binding. {@code BindingListeners}
+ * added to a {@code ColumnBinding} are notified at the time an edited {@code JTable} value
+ * is to be committed back to the source {@code List}. They receive notification of either
+ * {@code synced} or {@code syncFailed}. {@code BindingListeners} added to the
+ * {@code JTableBinding} itself are also notified of {@code sync} and {@code syncFailed}
+ * for the {@code JTableBinding's ColumnBindings}.
+ * <p>
+ * In addition to binding the elements of a {@code JTable}, it is possible to
+ * bind to the selection of a {@code JTable}. When binding to the selection of a {@code JTable}
+ * backed by a {@code JTableBinding}, the selection is always in terms of elements
+ * from the source {@code List}. See the list of <a href="package-summary.html#SWING_PROPS">
+ * interesting swing properties</a> in the package summary for more details.
+ *
+ * @param <E> the type of elements in the source {@code List}
+ * @param <SS> the type of source object (on which the source property resolves to {@code List})
+ * @param <TS> the type of target object (on which the target property resolves to {@code JTable})
+ *
+ * @author Shannon Hickey
+ */
+public final class JTableBinding<E, SS, TS> extends AutoBinding<SS, List<E>, TS, List> {
+
+    private Property<TS, ? extends JTable> tableP;
+    private ElementsProperty<TS> elementsP;
+    private Handler handler = new Handler();
+    private JTable table;
+    private BindingTableModel model;
+    private boolean editable = true;
+    private List<ColumnBinding> columnBindings = new ArrayList<ColumnBinding>();
+
+    /**
+     * Constructs an instance of {@code JTableBinding}.
+     *
+     * @param strategy the update strategy
+     * @param sourceObject the source object
+     * @param sourceListProperty a property on the source object that resolves to the {@code List} of elements
+     * @param targetObject the target object
+     * @param targetJTableProperty a property on the target object that resolves to a {@code JTable}
+     * @param name a name for the {@code JTableBinding}
+     * @throws IllegalArgumentException if the source property or target property is {@code null}
+     */
+    protected JTableBinding(UpdateStrategy strategy, SS sourceObject, Property<SS, List<E>> sourceListProperty, TS targetObject, Property<TS, ? extends JTable> targetJTableProperty, String name) {
+        super(strategy == READ_WRITE ? READ : strategy,
+              sourceObject, sourceListProperty, targetObject, new ElementsProperty<TS>(), name);
+
+        if (targetJTableProperty == null) {
+            throw new IllegalArgumentException("target JTable property can't be null");
+        }
+
+        tableP = targetJTableProperty;
+        elementsP = (ElementsProperty<TS>)getTargetProperty();
+    }
+
+    protected void bindImpl() {
+        elementsP.setAccessible(isTableAccessible());
+        tableP.addPropertyStateListener(getTargetObject(), handler);
+        elementsP.addPropertyStateListener(null, handler);
+        super.bindImpl();
+    }
+
+    protected void unbindImpl() {
+        elementsP.removePropertyStateListener(null, handler);
+        tableP.removePropertyStateListener(getTargetObject(), handler);
+        elementsP.setAccessible(false);
+        cleanupForLast();
+        super.unbindImpl();
+    }
+
+    private boolean isTableAccessible() {
+        return tableP.isReadable(getTargetObject()) && tableP.getValue(getTargetObject()) != null;
+    }
+
+    private boolean isTableAccessible(Object value) {
+        return value != null && value != PropertyStateEvent.UNREADABLE;
+    }
+
+    private void cleanupForLast() {
+        if (table == null) {
+            return;
+        }
+
+        table.setModel(new DefaultTableModel());
+        table = null;
+        model.setElements(null, true);
+        model = null;
+    }
+    
+    /**
+     * Sets whether or not the cells of the table should be editable.
+     * The default for this property is {@code true}.
+     * See this <a href="#EDITABILITY">paragraph</a> in the class level
+     * documentation on editability.
+     *
+     * @param editable whether or not the cells of the table should be editable
+     */
+    public void setEditable(boolean editable) {
+        this.editable = editable;
+    }
+
+    /**
+     * Returns whether or not the cells of the table should be editable.
+     * The default for this property is {@code true}.
+     * See this <a href="#EDITABILITY">paragraph</a> in the class level
+     * documentation on editability.
+     *
+     * @return whether or not the cells of the table should be editable
+     */
+    public boolean isEditable() {
+        return editable;
+    }
+
+    /**
+     * Creates a {@code ColumnBinding} and adds it to the end of the list of {@code ColumnBindings}
+     * maintained by this {@code JTableBinding}.
+     * <p>
+     * The list of {@code ColumnBindings} dictates the columns to be displayed in the
+     * {@code JTable}, with a {@code ColumnBinding's} order in the list determining its
+     * table model index.
+     *
+     * @param columnProperty the property with which to derive cell values from the
+     *                       elements of the source {@code List}
+     * @return the {@code ColumnBinding}
+     * @throws IllegalArgumentException if {@code columnProperty} is {@code null}
+     * @see org.jdesktop.swingbinding.JTableBinding.ColumnBinding
+     */
+    public ColumnBinding addColumnBinding(Property<E, ?> columnProperty) {
+        return addColumnBinding(columnProperty, null);
+    }
+
+    /**
+     * Creates a named {@code ColumnBinding} and adds it to the end of the list of {@code ColumnBindings}
+     * maintained by this {@code JTableBinding}.
+     * <p>
+     * The list of {@code ColumnBindings} dictates the columns to be displayed in the
+     * {@code JTable}, with a {@code ColumnBinding's} order in the list determining its
+     * table model index.
+     *
+     * @param columnProperty the property with which to derive cell values from the
+     *                       elements of the source {@code List}
+     * @param name a name for the column binding
+     * @return the {@code ColumnBinding}
+     * @throws IllegalArgumentException if {@code columnProperty} is {@code null}
+     * @see org.jdesktop.swingbinding.JTableBinding.ColumnBinding
+     */
+    public ColumnBinding addColumnBinding(Property<E, ?> columnProperty, String name) {
+        throwIfBound();
+
+        if (columnProperty == null) {
+            throw new IllegalArgumentException("can't have null column property");
+        }
+
+        if (name == null && JTableBinding.this.getName() != null) {
+            name = JTableBinding.this.getName() + ".COLUMN_BINDING";
+        }
+
+        ColumnBinding binding = new ColumnBinding(columnBindings.size(), columnProperty, name);
+        columnBindings.add(binding);
+        return binding;
+    }
+
+    /**
+     * Creates a {@code ColumnBinding} and inserts it at the given index into the list
+     * of {@code ColumnBindings} maintained by this {@code JTableBinding}.
+     * <p>
+     * The list of {@code ColumnBindings} dictates the columns to be displayed in the
+     * {@code JTable}, with a {@code ColumnBinding's} order in the list determining its
+     * table model index.
+     *
+     * @param index the index at which to insert the {@code ColumnBinding}
+     * @param columnProperty the property with which to derive cell values from the
+     *                       elements of the source {@code List}
+     * @return the {@code ColumnBinding}
+     * @throws IllegalArgumentException if {@code columnProperty} is {@code null}
+     * @see org.jdesktop.swingbinding.JTableBinding.ColumnBinding
+     */
+    public ColumnBinding addColumnBinding(int index, Property<E, ?> columnProperty) {
+        return addColumnBinding(index, columnProperty, null);
+    }
+
+    /**
+     * Creates a {@code ColumnBinding} and inserts it at the given index into the list
+     * of {@code ColumnBindings} maintained by this {@code JTableBinding}.
+     * <p>
+     * The list of {@code ColumnBindings} dictates the columns to be displayed in the
+     * {@code JTable}, with a {@code ColumnBinding's} order in the list determining its
+     * table model index.
+     *
+     * @param index the index at which to insert the {@code ColumnBinding}
+     * @param columnProperty the property with which to derive cell values from the
+     *                       elements of the source {@code List}
+     * @param name a name for the {@code ColumnBinding}
+     * @return the {@code ColumnBinding}
+     * @throws IllegalArgumentException if {@code columnProperty} is {@code null}
+     * @see org.jdesktop.swingbinding.JTableBinding.ColumnBinding
+     */
+    public ColumnBinding addColumnBinding(int index, Property<E, ?> columnProperty, String name) {
+        throwIfBound();
+
+        if (columnProperty == null) {
+            throw new IllegalArgumentException("can't have null column property");
+        }
+
+        if (name == null && JTableBinding.this.getName() != null) {
+            name = JTableBinding.this.getName() + ".COLUMN_BINDING";
+        }
+        
+        ColumnBinding binding = new ColumnBinding(index, columnProperty, name);
+        columnBindings.add(index, binding);
+        adjustIndices(index + 1, true);
+        return binding;
+    }
+
+    /**
+     * Removes the given {@code ColumnBinding} from the list maintained
+     * by this {@code JTableBinding}.
+     * <p>
+     * The list of {@code ColumnBindings} dictates the columns to be displayed in the
+     * {@code JTable}, with a {@code ColumnBinding's} order in the list determining its
+     * table model index.
+     *
+     * @param binding the {@code ColumnBinding} to remove
+     * @see #addColumnBinding(Property, String)
+     */
+    public boolean removeColumnBinding(ColumnBinding binding) {
+        throwIfBound();
+        boolean retVal = columnBindings.remove(binding);
+
+        if (retVal) {
+            adjustIndices(binding.getColumn(), false);
+        }
+
+        return retVal;
+    }
+
+    /**
+     * Removes the {@code ColumnBinding} with the given index from the list maintained
+     * by this {@code JTableBinding}.
+     * <p>
+     * The list of {@code ColumnBindings} dictates the columns to be displayed in the
+     * {@code JTable}, with a {@code ColumnBinding's} order in the list determining its
+     * table model index.
+     *
+     * @param index the index of the {@code ColumnBinding} to remove
+     * @see #addColumnBinding(Property, String)
+     */
+    public ColumnBinding removeColumnBinding(int index) {
+        throwIfBound();
+        ColumnBinding retVal = columnBindings.remove(index);
+        
+        if (retVal != null) {
+            adjustIndices(index, false);
+        }
+
+        return retVal;
+    }
+
+    /**
+     * Returns the {@code ColumnBinding} with the given index in the list maintained
+     * by this {@code JTableBinding}.
+     * <p>
+     * The list of {@code ColumnBindings} dictates the columns to be displayed in the
+     * {@code JTable}, with a {@code ColumnBinding's} order in the list determining its
+     * table model index.
+     *
+     * @param index the index of the {@code ColumnBinding} to return
+     * @return the {@code ColumnBinding} at the given index
+     * @see #addColumnBinding(Property, String)
+     */
+    public ColumnBinding getColumnBinding(int index) {
+        return columnBindings.get(index);
+    }
+
+    /**
+     * Returns an unmodifiable copy of the list of {@code ColumnBindings} maintained
+     * by this {@code JTableBinding}.
+     * <p>
+     * The list of {@code ColumnBindings} dictates the columns to be displayed in the
+     * {@code JTable}, with a {@code ColumnBinding's} order in the list determining its
+     * table model index.
+     *
+     * @return the list of {@code ColumnBindings}
+     * @see #addColumnBinding(Property, String)
+     */
+    public List<ColumnBinding> getColumnBindings() {
+        return Collections.unmodifiableList(columnBindings);
+    }
+
+    private void adjustIndices(int start, boolean up) {
+        int size = columnBindings.size();
+        for (int i = start; i < size; i++) {
+            ColumnBinding cb = columnBindings.get(i);
+            cb.adjustColumn(cb.getColumn() + (up ? 1 : -1));
+        }
+    }
+    
+    private final class ColumnProperty extends Property {
+        private ColumnBinding binding;
+
+        public Class<? extends Object> getWriteType(Object source) {
+            return binding.columnClass == null ? Object.class : binding.columnClass;
+        }
+
+        public Object getValue(Object source) {
+            if (binding.isBound()) {
+                return binding.editingObject;
+            }
+
+            throw new UnsupportedOperationException();
+        }
+
+        public void setValue(Object source, Object value) {
+            throw new UnsupportedOperationException();
+        }
+
+        public boolean isReadable(Object source) {
+            return binding.isBound();
+        }
+
+        public boolean isWriteable(Object source) {
+            return true;
+        }
+
+        public void addPropertyStateListener(Object source, PropertyStateListener listener) {
+        }
+
+        public void removePropertyStateListener(Object source, PropertyStateListener listener) {
+        }
+
+        public PropertyStateListener[] getPropertyStateListeners(Object source) {
+            return new PropertyStateListener[0];
+        }
+    }
+
+    /**
+     * {@code ColumnBinding} represents a binding between a property of the elements
+     * in the {@code JTableBinding's} source {@code List}, and a column in the table. Each
+     * {@code ColumnBinding} added to a {@code JTableBinding} represents a column
+     * to be displayed by the {@code JTable}. A value for any given row in a column
+     * is aquired by fetching the value of the associated {@code ColumnBinding's}
+     * source property for the element in the source {@code List} representing that row.
+     * <p>
+     * A {@code Converter} may be specified on a {@code ColumnBinding}, as may be
+     * a {@code Validator}. Validation occurs at the time a cell value is to be
+     * committed back to the source {@code List}.
+     * <p>
+     * {@code BindingListeners} registered on
+     * a {@code ColumnBinding} are notified of successful {@code sync} or
+     * {@code syncFailure}. These notifications are also sent to the
+     * {@code JTableBinding's} {@code BindingListeners}.
+     * <p>
+     * {@code ColumnBindings} are managed by their {@code JTableBinding}. They are not
+     * to be explicitly bound, unbound, added to a {@code BindingGroup}, or accessed
+     * in a way that is not allowed for a managed binding.
+     *
+     * @see org.jdesktop.swingbinding.JTableBinding#addColumnBinding(Property, String)
+     */
+    public final class ColumnBinding extends AbstractColumnBinding {
+        private Class<?> columnClass;
+        private boolean editable = true;
+        private boolean editableSet;
+        private String columnName;
+        private Object editingObject;
+
+        private ColumnBinding(int column, Property<E, ?> columnProperty, String name) {
+            super(column, columnProperty, new ColumnProperty(), name);
+            ((ColumnProperty) getTargetProperty()).binding = this;
+        }
+
+        private void setEditingObject(Object editingObject) {
+            this.editingObject = editingObject;
+        }
+        
+        private void adjustColumn(int newCol) {
+            setColumn(newCol);
+        }
+
+        /**
+         * Sets a name for the column represented by this {@code ColumnBinding}.
+         * This is used to initialize the table's column header name. If
+         * {@code null} is specified, the {@code toString()} value of the
+         * {@code ColumnBinding's} source property is used.
+         *
+         * @param name the name
+         * @return the {@code ColumnBinding} itself, to allow for method chaining
+         * @see javax.swing.table.TableModel#getColumnName
+         */
+        public ColumnBinding setColumnName(String name) {
+            JTableBinding.this.throwIfBound();
+            this.columnName = name;
+            return this;
+        }
+
+        /**
+         * Sets the column class to be used by {@code JTable} to determine
+         * the renderer and editor for the column represented by this
+         * {@code ColumnBinding}.
+         *
+         * @param columnClass the column class
+         * @return the {@code ColumnBinding} itself, to allow for method chaining
+         * @see javax.swing.table.TableModel#getColumnClass
+         */
+        public ColumnBinding setColumnClass(Class<?> columnClass) {
+            JTableBinding.this.throwIfBound();
+            this.columnClass = columnClass;
+            return this;
+        }
+
+        /**
+         * Returns the column class to be used by {@code JTable} to determine
+         * the renderer and editor for the column represented by this
+         * {@code ColumnBinding}.
+         *
+         * @see #setColumnClass
+         * @see javax.swing.table.TableModel#getColumnClass
+         */
+        public Class<?> getColumnClass() {
+            return columnClass == null ? Object.class : columnClass;
+        }
+
+        /**
+         * Returns the name for the column represented by this {@code ColumnBinding}.
+         * This is used to initialize the table's column header name.  If no name
+         * has been specified, or if it has been set to {@code null}, the
+         * {@code toString()} value of the {@code ColumnBinding's} source property is returned.
+         *
+         * @return the name for the column
+         * @see #setColumnName
+         * @see javax.swing.table.TableModel#getColumnName
+         */
+        public String getColumnName() {
+            return columnName == null ? getSourceProperty().toString() : columnName;
+        }
+
+        /**
+         * Sets whether or not the cells of the column should be editable.
+         * The default for this property is {@code true}.
+         * See this <a href="JTableBinding.html#EDITABILITY">paragraph</a> in the class level
+         * documentation on editability.
+         *
+         * @param editable whether or not the cells of the column should be editable
+         * @return the {@code ColumnBinding} itself, to allow for method chaining
+         */
+        public ColumnBinding setEditable(boolean editable) {
+            this.editable = editable;
+            return this;
+        }
+
+        /**
+         * Returns whether or not the cells of the column should be editable.
+         * The default for this property is {@code true}.
+         * See this <a href="JTableBinding.html#EDITABILITY">paragraph</a> in the class level
+         * documentation on editability.
+         *
+         * @return whether or not the cells of the column should be editable
+         */
+        public boolean isEditable() {
+            return editable;
+        }
+
+        private void bindUnmanaged0() {
+            bindUnmanaged();
+        }
+        
+        private void unbindUnmanaged0() {
+            unbindUnmanaged();
+        }
+
+        private SyncFailure saveUnmanaged0() {
+            return saveUnmanaged();
+        }
+
+        private void setSourceObjectUnmanaged0(Object source) {
+            setSourceObjectUnmanaged(source);
+        }
+    }
+
+    private class Handler implements PropertyStateListener {
+        public void propertyStateChanged(PropertyStateEvent pse) {
+            if (!pse.getValueChanged()) {
+                return;
+            }
+
+            if (pse.getSourceProperty() == tableP) {
+                cleanupForLast();
+                
+                boolean wasAccessible = isTableAccessible(pse.getOldValue());
+                boolean isAccessible = isTableAccessible(pse.getNewValue());
+
+                if (wasAccessible != isAccessible) {
+                    elementsP.setAccessible(isAccessible);
+                } else if (elementsP.isAccessible()) {
+                    elementsP.setValueAndIgnore(null, null);
+                }
+            } else {
+                if (((ElementsProperty.ElementsPropertyStateEvent)pse).shouldIgnore()) {
+                    return;
+                }
+
+                if (table == null) {
+                    table = tableP.getValue(getTargetObject());
+                    model = new BindingTableModel();
+                    table.setModel(model);
+                }
+
+                model.setElements((List)pse.getNewValue(), true);
+            }
+        }
+    }
+
+    private final class BindingTableModel extends ListBindingManager implements TableModel  {
+        private final List<TableModelListener> listeners;
+
+        public BindingTableModel() {
+            listeners = new CopyOnWriteArrayList<TableModelListener>();
+        }
+
+        protected AbstractColumnBinding[] getColBindings() {
+            AbstractColumnBinding[] bindings = new AbstractColumnBinding[getColumnBindings().size()];
+            bindings = getColumnBindings().toArray(bindings);
+            return bindings;
+        }
+
+        public int getRowCount() {
+            return size();
+        }
+
+        public Object getValueAt(int rowIndex, int columnIndex) {
+            return valueAt(rowIndex, columnIndex);
+        }
+
+        public void setValueAt(Object value, int rowIndex, int columnIndex) {
+            ColumnBinding cb = JTableBinding.this.getColumnBinding(columnIndex);
+            BindingListener[] cbListeners = cb.getBindingListeners();
+            BindingListener[] tbListeners = getBindingListeners();
+            
+            cb.setSourceObjectUnmanaged0(this.getElement(rowIndex));
+            cb.setEditingObject(value);
+            cb.bindUnmanaged0();
+            
+            for (BindingListener listener : tbListeners) {
+                listener.bindingBecameBound(cb);
+            }
+            
+            PropertyStateEvent pse = new PropertyStateEvent(cb.getTargetProperty(),
+                    cb.getTargetObject(),
+                    true,
+                    getValueAt(rowIndex, columnIndex),
+                    value,
+                    false,
+                    cb.getSourceProperty().isWriteable(cb.getSourceObject()));
+            
+            for (BindingListener listener : cbListeners) {
+                listener.targetChanged(cb, pse);
+            }
+            
+            for (BindingListener listener : tbListeners) {
+                listener.targetChanged(cb, pse);
+            }
+            
+            SyncFailure failure = cb.saveUnmanaged0();
+            
+            if (failure == null) {
+                for (BindingListener listener : cbListeners) {
+                    listener.synced(cb);
+                }
+                
+                for (BindingListener listener : tbListeners) {
+                    listener.synced(cb);
+                }
+            } else {
+                for (BindingListener listener : cbListeners) {
+                    listener.syncFailed(cb, failure);
+                }
+                
+                for (BindingListener listener : tbListeners) {
+                    listener.syncFailed(cb, failure);
+                }
+            }
+
+            cb.unbindUnmanaged0();
+            
+            for (BindingListener listener : tbListeners) {
+                listener.bindingBecameUnbound(cb);
+            }
+            
+            cb.setEditingObject(null);
+            cb.setSourceObjectUnmanaged0(null);
+        }
+
+        public Class<?> getColumnClass(int columnIndex) {
+            Class<?> klass = JTableBinding.this.getColumnBinding(columnIndex).getColumnClass();
+            return klass == null ? Object.class : klass;
+        }
+
+        protected void allChanged() {
+            fireTableModelEvent(new TableModelEvent(this, 0, Integer.MAX_VALUE));
+        }
+
+        protected void valueChanged(int row, int column) {
+            fireTableModelEvent(new TableModelEvent(this, row, row, column));
+        }
+
+        protected void added(int row, int length) {
+            assert length > 0; // enforced by ListBindingManager
+
+            fireTableModelEvent(new TableModelEvent(this, row, row + length - 1, TableModelEvent.ALL_COLUMNS, TableModelEvent.INSERT));
+        }
+
+        protected void removed(int row, int length) {
+            assert length > 0; // enforced by ListBindingManager
+
+            fireTableModelEvent(new TableModelEvent(this, row, row + length - 1, TableModelEvent.ALL_COLUMNS, TableModelEvent.DELETE));
+        }
+
+        protected void changed(int row) {
+            fireTableModelEvent(new TableModelEvent(this, row, row, TableModelEvent.ALL_COLUMNS));
+        }
+
+        public String getColumnName(int columnIndex) {
+            ColumnBinding binding = JTableBinding.this.getColumnBinding(columnIndex);
+            return binding.getColumnName() == null ? binding.getSourceProperty().toString() : binding.getColumnName();
+        }
+
+        public boolean isCellEditable(int rowIndex, int columnIndex) {
+            if (!JTableBinding.this.isEditable()) {
+                return false;
+            }
+
+            ColumnBinding binding = JTableBinding.this.getColumnBinding(columnIndex);
+            if (!binding.isEditable()) {
+                return false;
+            }
+
+            return binding.getSourceProperty().isWriteable(getElement(rowIndex));
+        }
+
+        public void addTableModelListener(TableModelListener l) {
+            listeners.add(l);
+        }
+
+        public void removeTableModelListener(TableModelListener l) {
+            listeners.remove(l);
+        }
+
+        private void fireTableModelEvent(TableModelEvent e) {
+            for (TableModelListener listener : listeners) {
+                listener.tableChanged(e);
+            }
+        }
+
+        public int getColumnCount() {
+            return columnCount();
+        }
+    }
+
+}
diff --git a/src/org/jdesktop/swingbinding/SwingBindings.java b/src/org/jdesktop/swingbinding/SwingBindings.java
new file mode 100644
index 0000000..4a01e69
--- /dev/null
+++ b/src/org/jdesktop/swingbinding/SwingBindings.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.swingbinding;
+
+import java.util.*;
+import javax.swing.*;
+import org.jdesktop.beansbinding.AutoBinding;
+import org.jdesktop.beansbinding.ObjectProperty;
+import org.jdesktop.beansbinding.Property;
+
+/**
+ * A factory class for creating instances of the custom Swing {@code Binding}
+ * implementations provided by this package. See the
+ * <a href="package-summary.html">package summary</a> for full details on
+ * binding to Swing components.
+ *
+ * @author Shannon Hickey
+ */
+public class SwingBindings {
+    
+    private SwingBindings() {}
+    
+    /**
+     * Creates a {@code JListBinding} from direct references to a {@code List} and {@code JList}.
+     *
+     * @param strategy the update strategy
+     * @param sourceList the source {@code List}
+     * @param targetJList the target {@code JList}
+     * @return the {@code JTableBinding}
+     */
+    public static <E> JListBinding<E, List<E>, JList> createJListBinding(AutoBinding.UpdateStrategy strategy, List<E> sourceList, JList targetJList) {
+        return new JListBinding<E, List<E>, JList>(strategy, sourceList, ObjectProperty.<List<E>>create(), targetJList, ObjectProperty.<JList>create(), null);
+    }
+    
+    /**
+     * Creates a named {@code JListBinding} from direct references to a {@code List} and {@code JList}.
+     *
+     * @param strategy the update strategy
+     * @param sourceList the source {@code List}
+     * @param targetJList the target {@code JList}
+     * @return the {@code JListBinding}
+     */
+    public static <E> JListBinding<E, List<E>, JList> createJListBinding(AutoBinding.UpdateStrategy strategy, List<E> sourceList, JList targetJList, String name) {
+        return new JListBinding<E, List<E>, JList>(strategy, sourceList, ObjectProperty.<List<E>>create(), targetJList, ObjectProperty.<JList>create(), name);
+    }
+    
+    
+    /**
+     * Creates a {@code JListBinding} from an object and property that resolves to a {@code List} and a direct reference to a {@code JList}.
+     *
+     * @param strategy the update strategy
+     * @param sourceObject the source object
+     * @param sourceListProperty a property on the source object that resolves to a {@code List}
+     * @param targetJList the target {@code JList}
+     * @return the {@code JListBinding}
+     * @throws IllegalArgumentException if {@code sourceListProperty} is {@code null}
+     */
+    public static <E, SS> JListBinding<E, SS, JList> createJListBinding(AutoBinding.UpdateStrategy strategy, SS sourceObject, Property<SS, List<E>> sourceListProperty, JList targetJList) {
+        return new JListBinding<E, SS, JList>(strategy, sourceObject, sourceListProperty, targetJList, ObjectProperty.<JList>create(), null);
+    }
+    
+    /**
+     * Creates a named {@code JListBinding} from an object and property that resolves to a {@code List} and a direct reference to a {@code JList}.
+     *
+     * @param strategy the update strategy
+     * @param sourceObject the source object
+     * @param sourceListProperty a property on the source object that resolves to a {@code List}
+     * @param targetJList the target {@code JList}
+     * @return the {@code JListBinding}
+     * @throws IllegalArgumentException if {@code sourceListProperty} is {@code null}
+     */
+    public static <E, SS> JListBinding<E, SS, JList> createJListBinding(AutoBinding.UpdateStrategy strategy, SS sourceObject, Property<SS, List<E>> sourceListProperty, JList targetJList, String name) {
+        return new JListBinding<E, SS, JList>(strategy, sourceObject, sourceListProperty, targetJList, ObjectProperty.<JList>create(), name);
+    }
+    
+    
+    /**
+     * Creates a {@code JListBinding} from a direct reference to a {@code List} and an object and property that resolves to a {@code JList}.
+     *
+     * @param strategy the update strategy
+     * @param sourceList the source {@code List}
+     * @param targetObject the target object
+     * @param targetJListProperty a property on the target object that resolves to a {@code JList}
+     * @return the {@code JListBinding}
+     * @throws IllegalArgumentException if {@code targetJListProperty} is {@code null}
+     */
+    public static <E, TS> JListBinding<E, List<E>, TS> createJListBinding(AutoBinding.UpdateStrategy strategy, List<E> sourceList, TS targetObject, Property<TS, ? extends JList> targetJListProperty) {
+        return new JListBinding<E, List<E>, TS>(strategy, sourceList, ObjectProperty.<List<E>>create(), targetObject, targetJListProperty, null);
+    }
+    
+    /**
+     * Creates a named {@code JListBinding} from a direct reference to a {@code List} and an object and property that resolves to a {@code JList}.
+     *
+     * @param strategy the update strategy
+     * @param sourceList the source {@code List}
+     * @param targetObject the target object
+     * @param targetJListProperty a property on the target object that resolves to a {@code JList}
+     * @return the {@code JListBinding}
+     * @throws IllegalArgumentException if {@code targetJListProperty} is {@code null}
+     */
+    public static <E, TS> JListBinding<E, List<E>, TS> createJListBinding(AutoBinding.UpdateStrategy strategy, List<E> sourceList, TS targetObject, Property<TS, ? extends JList> targetJListProperty, String name) {
+        return new JListBinding<E, List<E>, TS>(strategy, sourceList, ObjectProperty.<List<E>>create(), targetObject, targetJListProperty, name);
+    }
+    
+    
+    /**
+     * Creates a {@code JListBinding} from an object and property that resolves to a {@code List} and an object and property that resolves to a {@code JList}.
+     *
+     * @param strategy the update strategy
+     * @param sourceObject the source object
+     * @param sourceListProperty a property on the source object that resolves to a {@code List}
+     * @param targetObject the target object
+     * @param targetJListProperty a property on the target object that resolves to a {@code JList}
+     * @return the {@code JListBinding}
+     * @throws IllegalArgumentException if {@code sourceListProperty} or {@code targetJListProperty} is {@code null}
+     */
+    public static <E, SS, TS> JListBinding<E, SS, TS> createJListBinding(AutoBinding.UpdateStrategy strategy, SS sourceObject, Property<SS, List<E>> sourceListProperty, TS targetObject, Property<TS, ? extends JList> targetJListProperty) {
+        return new JListBinding<E, SS, TS>(strategy, sourceObject, sourceListProperty, targetObject, targetJListProperty, null);
+    }
+    
+    /**
+     * Creates a named {@code JListBinding} from an object and property that resolves to a {@code List} and an object and property that resolves to a {@code JList}.
+     *
+     * @param strategy the update strategy
+     * @param sourceObject the source object
+     * @param sourceListProperty a property on the source object that resolves to a {@code List}
+     * @param targetObject the target object
+     * @param targetJListProperty a property on the target object that resolves to a {@code JList}
+     * @return the {@code JListBinding}
+     * @throws IllegalArgumentException if {@code sourceListProperty} or {@code targetJListProperty} is {@code null}
+     */
+    public static <E, SS, TS> JListBinding<E, SS, TS> createJListBinding(AutoBinding.UpdateStrategy strategy, SS sourceObject, Property<SS, List<E>> sourceListProperty, TS targetObject, Property<TS, ? extends JList> targetJListProperty, String name) {
+        return new JListBinding<E, SS, TS>(strategy, sourceObject, sourceListProperty, targetObject, targetJListProperty, name);
+    }
+    
+    
+    
+    /**
+     * Creates a {@code JTableBinding} from direct references to a {@code List} and {@code JTable}.
+     *
+     * @param strategy the update strategy
+     * @param sourceList the source {@code List}
+     * @param targetJTable the target {@code JTable}
+     * @return the {@code JTableBinding}
+     */
+    public static <E> JTableBinding<E, List<E>, JTable> createJTableBinding(AutoBinding.UpdateStrategy strategy, List<E> sourceList, JTable targetJTable) {
+        return new JTableBinding<E, List<E>, JTable>(strategy, sourceList, ObjectProperty.<List<E>>create(), targetJTable, ObjectProperty.<JTable>create(), null);
+    }
+    
+    /**
+     * Creates a named {@code JTableBinding} from direct references to a {@code List} and {@code JTable}.
+     *
+     * @param strategy the update strategy
+     * @param sourceList the source {@code List}
+     * @param targetJTable the target {@code JTable}
+     * @return the {@code JTableBinding}
+     */
+    public static <E> JTableBinding<E, List<E>, JTable> createJTableBinding(AutoBinding.UpdateStrategy strategy, List<E> sourceList, JTable targetJTable, String name) {
+        return new JTableBinding<E, List<E>, JTable>(strategy, sourceList, ObjectProperty.<List<E>>create(), targetJTable, ObjectProperty.<JTable>create(), name);
+    }
+    
+    
+    /**
+     * Creates a {@code JTableBinding} from an object and property that resolves to a {@code List} and a direct reference to a {@code JTable}.
+     *
+     * @param strategy the update strategy
+     * @param sourceObject the source object
+     * @param sourceListProperty a property on the source object that resolves to a {@code List}
+     * @param targetJTable the target {@code JTable}
+     * @return the {@code JTableBinding}
+     * @throws IllegalArgumentException if {@code sourceListProperty} is {@code null}
+     */
+    public static <E, SS> JTableBinding<E, SS, JTable> createJTableBinding(AutoBinding.UpdateStrategy strategy, SS sourceObject, Property<SS, List<E>> sourceListProperty, JTable targetJTable) {
+        return new JTableBinding<E, SS, JTable>(strategy, sourceObject, sourceListProperty, targetJTable, ObjectProperty.<JTable>create(), null);
+    }
+    
+    /**
+     * Creates a named {@code JTableBinding} from an object and property that resolves to a {@code List} and a direct reference to a {@code JTable}.
+     *
+     * @param strategy the update strategy
+     * @param sourceObject the source object
+     * @param sourceListProperty a property on the source object that resolves to a {@code List}
+     * @param targetJTable the target {@code JTable}
+     * @return the {@code JTableBinding}
+     * @throws IllegalArgumentException if {@code sourceListProperty} is {@code null}
+     */
+    public static <E, SS> JTableBinding<E, SS, JTable> createJTableBinding(AutoBinding.UpdateStrategy strategy, SS sourceObject, Property<SS, List<E>> sourceListProperty, JTable targetJTable, String name) {
+        return new JTableBinding<E, SS, JTable>(strategy, sourceObject, sourceListProperty, targetJTable, ObjectProperty.<JTable>create(), name);
+    }
+    
+    
+    /**
+     * Creates a {@code JTableBinding} from a direct reference to a {@code List} and an object and property that resolves to a {@code JTable}.
+     *
+     * @param strategy the update strategy
+     * @param sourceList the source {@code List}
+     * @param targetObject the target object
+     * @param targetJTableProperty a property on the target object that resolves to a {@code JTable}
+     * @return the {@code JTableBinding}
+     * @throws IllegalArgumentException if {@code targetJTableProperty} is {@code null}
+     */
+    public static <E, TS> JTableBinding<E, List<E>, TS> createJTableBinding(AutoBinding.UpdateStrategy strategy, List<E> sourceList, TS targetObject, Property<TS, ? extends JTable> targetJTableProperty) {
+        return new JTableBinding<E, List<E>, TS>(strategy, sourceList, ObjectProperty.<List<E>>create(), targetObject, targetJTableProperty, null);
+    }
+    
+    /**
+     * Creates a named {@code JTableBinding} from a direct reference to a {@code List} and an object and property that resolves to a {@code JTable}.
+     *
+     * @param strategy the update strategy
+     * @param sourceList the source {@code List}
+     * @param targetObject the target object
+     * @param targetJTableProperty a property on the target object that resolves to a {@code JTable}
+     * @return the {@code JTableBinding}
+     * @throws IllegalArgumentException if {@code targetJTableProperty} is {@code null}
+     */
+    public static <E, TS> JTableBinding<E, List<E>, TS> createJTableBinding(AutoBinding.UpdateStrategy strategy, List<E> sourceList, TS targetObject, Property<TS, ? extends JTable> targetJTableProperty, String name) {
+        return new JTableBinding<E, List<E>, TS>(strategy, sourceList, ObjectProperty.<List<E>>create(), targetObject, targetJTableProperty, name);
+    }
+    
+    
+    /**
+     * Creates a {@code JTableBinding} from an object and property that resolves to a {@code List} and an object and property that resolves to a {@code JTable}.
+     *
+     * @param strategy the update strategy
+     * @param sourceObject the source object
+     * @param sourceListProperty a property on the source object that resolves to a {@code List}
+     * @param targetObject the target object
+     * @param targetJTableProperty a property on the target object that resolves to a {@code JTable}
+     * @return the {@code JTableBinding}
+     * @throws IllegalArgumentException if {@code sourceListProperty} or {@code targetJTableProperty} is {@code null}
+     */
+    public static <E, SS, TS> JTableBinding<E, SS, TS> createJTableBinding(AutoBinding.UpdateStrategy strategy, SS sourceObject, Property<SS, List<E>> sourceListProperty, TS targetObject, Property<TS, ? extends JTable> targetJTableProperty) {
+        return new JTableBinding<E, SS, TS>(strategy, sourceObject, sourceListProperty, targetObject, targetJTableProperty, null);
+    }
+    
+    /**
+     * Creates a named {@code JTableBinding} from an object and property that resolves to a {@code List} and an object and property that resolves to a {@code JTable}.
+     *
+     * @param strategy the update strategy
+     * @param sourceObject the source object
+     * @param sourceListProperty a property on the source object that resolves to a {@code List}
+     * @param targetObject the target object
+     * @param targetJTableProperty a property on the target object that resolves to a {@code JTable}
+     * @return the {@code JTableBinding}
+     * @throws IllegalArgumentException if {@code sourceListProperty} or {@code targetJTableProperty} is {@code null}
+     */
+    public static <E, SS, TS> JTableBinding<E, SS, TS> createJTableBinding(AutoBinding.UpdateStrategy strategy, SS sourceObject, Property<SS, List<E>> sourceListProperty, TS targetObject, Property<TS, ? extends JTable> targetJTableProperty, String name) {
+        return new JTableBinding<E, SS, TS>(strategy, sourceObject, sourceListProperty, targetObject, targetJTableProperty, name);
+    }
+    
+    
+    
+    /**
+     * Creates a {@code JComboBoxBinding} from direct references to a {@code List} and {@code JComboBox}.
+     *
+     * @param strategy the update strategy
+     * @param sourceList the source {@code List}
+     * @param targetJComboBox the target {@code JComboBox}
+     * @return the {@code JComboBoxBinding}
+     */
+    public static <E> JComboBoxBinding<E, List<E>, JComboBox> createJComboBoxBinding(AutoBinding.UpdateStrategy strategy, List<E> sourceList, JComboBox targetJComboBox) {
+        return new JComboBoxBinding<E, List<E>, JComboBox>(strategy, sourceList, ObjectProperty.<List<E>>create(), targetJComboBox, ObjectProperty.<JComboBox>create(), null);
+    }
+    
+    /**
+     * Creates a named {@code JComboBoxBinding} from direct references to a {@code List} and {@code JComboBox}.
+     *
+     * @param strategy the update strategy
+     * @param sourceList the source {@code List}
+     * @param targetJComboBox the target {@code JComboBox}
+     * @return the {@code JComboBoxBinding}
+     */
+    public static <E> JComboBoxBinding<E, List<E>, JComboBox> createJComboBoxBinding(AutoBinding.UpdateStrategy strategy, List<E> sourceList, JComboBox targetJComboBox, String name) {
+        return new JComboBoxBinding<E, List<E>, JComboBox>(strategy, sourceList, ObjectProperty.<List<E>>create(), targetJComboBox, ObjectProperty.<JComboBox>create(), name);
+    }
+    
+    
+    /**
+     * Creates a {@code JComboBoxBinding} from an object and property that resolves to a {@code List} and a direct reference to a {@code JComboBox}.
+     *
+     * @param strategy the update strategy
+     * @param sourceObject the source object
+     * @param sourceListProperty a property on the source object that resolves to a {@code List}
+     * @param targetJComboBox the target {@code JComboBox}
+     * @return the {@code JComboBoxBinding}
+     * @throws IllegalArgumentException if {@code sourceListProperty} is {@code null}
+     */
+    public static <E, SS> JComboBoxBinding<E, SS, JComboBox> createJComboBoxBinding(AutoBinding.UpdateStrategy strategy, SS sourceObject, Property<SS, List<E>> sourceListProperty, JComboBox targetJComboBox) {
+        return new JComboBoxBinding<E, SS, JComboBox>(strategy, sourceObject, sourceListProperty, targetJComboBox, ObjectProperty.<JComboBox>create(), null);
+    }
+    
+    /**
+     * Creates a named {@code JComboBoxBinding} from an object and property that resolves to a {@code List} and a direct reference to a {@code JComboBox}.
+     *
+     * @param strategy the update strategy
+     * @param sourceObject the source object
+     * @param sourceListProperty a property on the source object that resolves to a {@code List}
+     * @param targetJComboBox the target {@code JComboBox}
+     * @return the {@code JComboBoxBinding}
+     * @throws IllegalArgumentException if {@code sourceListProperty} is {@code null}
+     */
+    public static <E, SS> JComboBoxBinding<E, SS, JComboBox> createJComboBoxBinding(AutoBinding.UpdateStrategy strategy, SS sourceObject, Property<SS, List<E>> sourceListProperty, JComboBox targetJComboBox, String name) {
+        return new JComboBoxBinding<E, SS, JComboBox>(strategy, sourceObject, sourceListProperty, targetJComboBox, ObjectProperty.<JComboBox>create(), name);
+    }
+    
+    
+    /**
+     * Creates a {@code JComboBoxBinding} from a direct reference to a {@code List} and an object and property that resolves to a {@code JComboBox}.
+     *
+     * @param strategy the update strategy
+     * @param sourceList the source {@code List}
+     * @param targetObject the target object
+     * @param targetJComboBoxProperty a property on the target object that resolves to a {@code JComboBox}
+     * @return the {@code JComboBoxBinding}
+     * @throws IllegalArgumentException if {@code targetJComboBoxProperty} is {@code null}
+     */
+    public static <E, TS> JComboBoxBinding<E, List<E>, TS> createJComboBoxBinding(AutoBinding.UpdateStrategy strategy, List<E> sourceList, TS targetObject, Property<TS, ? extends JComboBox> targetJComboBoxProperty) {
+        return new JComboBoxBinding<E, List<E>, TS>(strategy, sourceList, ObjectProperty.<List<E>>create(), targetObject, targetJComboBoxProperty, null);
+    }
+    
+    /**
+     * Creates a named {@code JComboBoxBinding} from a direct reference to a {@code List} and an object and property that resolves to a {@code JComboBox}.
+     *
+     * @param strategy the update strategy
+     * @param sourceList the source {@code List}
+     * @param targetObject the target object
+     * @param targetJComboBoxProperty a property on the target object that resolves to a {@code JComboBox}
+     * @return the {@code JComboBoxBinding}
+     * @throws IllegalArgumentException if {@code targetJComboBoxProperty} is {@code null}
+     */
+    public static <E, TS> JComboBoxBinding<E, List<E>, TS> createJComboBoxBinding(AutoBinding.UpdateStrategy strategy, List<E> sourceList, TS targetObject, Property<TS, ? extends JComboBox> targetJComboBoxProperty, String name) {
+        return new JComboBoxBinding<E, List<E>, TS>(strategy, sourceList, ObjectProperty.<List<E>>create(), targetObject, targetJComboBoxProperty, name);
+    }
+    
+    
+    /**
+     * Creates a {@code JComboBoxBinding} from an object and property that resolves to a {@code List} and an object and property that resolves to a {@code JComboBox}.
+     *
+     * @param strategy the update strategy
+     * @param sourceObject the source object
+     * @param sourceListProperty a property on the source object that resolves to a {@code List}
+     * @param targetObject the target object
+     * @param targetJComboBoxProperty a property on the target object that resolves to a {@code JComboBox}
+     * @return the {@code JComboBoxBinding}
+     * @throws IllegalArgumentException if {@code sourceListProperty} or {@code targetJComboBoxProperty} is {@code null}
+     */
+    public static <E, SS, TS> JComboBoxBinding<E, SS, TS> createJComboBoxBinding(AutoBinding.UpdateStrategy strategy, SS sourceObject, Property<SS, List<E>> sourceListProperty, TS targetObject, Property<TS, ? extends JComboBox> targetJComboBoxProperty) {
+        return new JComboBoxBinding<E, SS, TS>(strategy, sourceObject, sourceListProperty, targetObject, targetJComboBoxProperty, null);
+    }
+    
+    /**
+     * Creates a named {@code JComboBoxBinding} from an object and property that resolves to a {@code List} and an object and property that resolves to a {@code JComboBox}.
+     *
+     * @param strategy the update strategy
+     * @param sourceObject the source object
+     * @param sourceListProperty a property on the source object that resolves to a {@code List}
+     * @param targetObject the target object
+     * @param targetJComboBoxProperty a property on the target object that resolves to a {@code JComboBox}
+     * @return the {@code JComboBoxBinding}
+     * @throws IllegalArgumentException if {@code sourceListProperty} or {@code targetJComboBoxProperty} is {@code null}
+     */
+    public static <E, SS, TS> JComboBoxBinding<E, SS, TS> createJComboBoxBinding(AutoBinding.UpdateStrategy strategy, SS sourceObject, Property<SS, List<E>> sourceListProperty, TS targetObject, Property<TS, ? extends JComboBox> targetJComboBoxProperty, String name) {
+        return new JComboBoxBinding<E, SS, TS>(strategy, sourceObject, sourceListProperty, targetObject, targetJComboBoxProperty, name);
+    }
+    
+}
diff --git a/src/org/jdesktop/swingbinding/adapters/AbstractButtonAdapterProvider.java b/src/org/jdesktop/swingbinding/adapters/AbstractButtonAdapterProvider.java
new file mode 100644
index 0000000..561a31e
--- /dev/null
+++ b/src/org/jdesktop/swingbinding/adapters/AbstractButtonAdapterProvider.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.swingbinding.adapters;
+
+import javax.swing.*;
+import java.awt.event.*;
+import java.beans.*;
+import org.jdesktop.beansbinding.ext.BeanAdapterProvider;
+
+/**
+ * @author Shannon Hickey
+ */
+public final class AbstractButtonAdapterProvider implements BeanAdapterProvider {
+
+    private static final String SELECTED_P = "selected";
+
+    public static final class Adapter extends BeanAdapterBase {
+        private AbstractButton button;
+        private Handler handler;
+        private boolean cachedSelected;
+
+        private Adapter(AbstractButton button) {
+            super(SELECTED_P);
+            this.button = button;
+        }
+
+        public boolean isSelected() {
+            return button.isSelected();
+        }
+
+        public void setSelected(boolean selected) {
+            button.setSelected(selected);
+        }
+
+        protected void listeningStarted() {
+            handler = new Handler();
+            cachedSelected = isSelected();
+            button.addItemListener(handler);
+            button.addPropertyChangeListener("model", handler);
+        }
+
+        protected void listeningStopped() {
+            button.removeItemListener(handler);
+            button.removePropertyChangeListener("model", handler);
+            handler = null;
+        }
+        
+        private class Handler implements ItemListener, PropertyChangeListener {
+            private void buttonSelectedChanged() {
+                boolean oldSelected = cachedSelected;
+                cachedSelected = isSelected();
+                firePropertyChange(oldSelected, cachedSelected);
+            }
+            
+            public void itemStateChanged(ItemEvent ie) {
+                buttonSelectedChanged();
+            }
+
+            public void propertyChange(PropertyChangeEvent pe) {
+                buttonSelectedChanged();
+            }
+        }
+    }
+
+    public boolean providesAdapter(Class<?> type, String property) {
+        return AbstractButton.class.isAssignableFrom(type) && property.intern() == SELECTED_P;
+    }
+
+    public Object createAdapter(Object source, String property) {
+        if (!providesAdapter(source.getClass(), property)) {
+            throw new IllegalArgumentException();
+        }
+
+        return new Adapter((AbstractButton)source);
+    }
+
+    public Class<?> getAdapterClass(Class<?> type) {
+        return AbstractButton.class.isAssignableFrom(type) ?
+            AbstractButtonAdapterProvider.Adapter.class :
+            null;
+    }
+
+}
diff --git a/src/org/jdesktop/swingbinding/adapters/BeanAdapterBase.java b/src/org/jdesktop/swingbinding/adapters/BeanAdapterBase.java
new file mode 100644
index 0000000..95b5147
--- /dev/null
+++ b/src/org/jdesktop/swingbinding/adapters/BeanAdapterBase.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.swingbinding.adapters;
+
+import java.beans.*;
+
+/**
+ * @author Shannon Hickey
+ */
+public class BeanAdapterBase {
+    protected final String property;
+    private PropertyChangeSupport support;
+
+    protected BeanAdapterBase(String property) {
+        assert property != null;
+        this.property = property.intern();
+    }
+
+    protected void listeningStarted() {}
+    protected void listeningStopped() {}
+
+    protected final boolean isListening() {
+        return support == null ? false : support.getPropertyChangeListeners().length > 0;
+    }
+
+    public final void addPropertyChangeListener(PropertyChangeListener listener) {
+        if (listener == null) {
+            return;
+        }
+
+        boolean wasListening = isListening();
+
+        if (support == null) {
+            support = new PropertyChangeSupport(this);
+        }
+
+        support.addPropertyChangeListener(listener);
+
+        if (!wasListening) {
+            listeningStarted();
+        }
+    }
+
+    public final void removePropertyChangeListener(PropertyChangeListener listener) {
+        if (listener == null || support == null) {
+            return;
+        }
+
+        boolean wasListening = isListening();
+        support.removePropertyChangeListener(listener);
+
+        if (wasListening && !isListening()) {
+            listeningStopped();
+        }
+    }
+
+    public final PropertyChangeListener[] getPropertyChangeListeners() {
+        if (support == null) {
+            return new PropertyChangeListener[0];
+        }
+
+        return support.getPropertyChangeListeners();
+    }
+
+    public final void addPropertyChangeListener(String property, PropertyChangeListener listener) {
+        if (listener == null || property == null || property.intern() != this.property) {
+            return;
+        }
+
+        boolean wasListening = isListening();
+
+        if (support == null) {
+            support = new PropertyChangeSupport(this);
+        }
+
+        support.addPropertyChangeListener(property, listener);
+
+        if (!wasListening) {
+            listeningStarted();
+        }
+    }
+
+    public final void removePropertyChangeListener(String property, PropertyChangeListener listener) {
+        if (listener == null || support == null || property == null || property.intern() != this.property) {
+            return;
+        }
+
+        boolean wasListening = isListening();
+        support.removePropertyChangeListener(property, listener);
+
+        if (wasListening && !isListening()) {
+            listeningStopped();
+        }
+    }
+
+    public final PropertyChangeListener[] getPropertyChangeListeners(String property) {
+        if (support == null || property == null || property.intern() != this.property) {
+            return new PropertyChangeListener[0];
+        }
+
+        return support.getPropertyChangeListeners(property);
+    }
+    
+    protected final void firePropertyChange(Object oldValue, Object newValue) {
+        if (support == null) {
+            return;
+        }
+
+        support.firePropertyChange(property, oldValue, newValue);
+    }
+
+}
diff --git a/src/org/jdesktop/swingbinding/adapters/JComboBoxAdapterProvider.java b/src/org/jdesktop/swingbinding/adapters/JComboBoxAdapterProvider.java
new file mode 100644
index 0000000..d26640e
--- /dev/null
+++ b/src/org/jdesktop/swingbinding/adapters/JComboBoxAdapterProvider.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.swingbinding.adapters;
+
+import javax.swing.*;
+import java.awt.event.*;
+import java.beans.*;
+import org.jdesktop.beansbinding.ext.BeanAdapterProvider;
+
+/**
+ * @author Shannon Hickey
+ */
+public final class JComboBoxAdapterProvider implements BeanAdapterProvider {
+
+    private static final String SELECTED_ITEM_P = "selectedItem";
+
+    public static final class Adapter extends BeanAdapterBase {
+        private JComboBox combo;
+        private Handler handler;
+        private Object cachedItem;
+
+        private Adapter(JComboBox combo) {
+            super(SELECTED_ITEM_P);
+            this.combo = combo;
+        }
+
+        public Object getSelectedItem() {
+            return combo.getSelectedItem();
+        }
+
+        public void setSelectedItem(Object item) {
+            combo.setSelectedItem(item);
+        }
+        
+        protected void listeningStarted() {
+            handler = new Handler();
+            cachedItem = combo.getSelectedItem();
+            combo.addActionListener(handler);
+            combo.addPropertyChangeListener("model", handler);
+        }
+
+        protected void listeningStopped() {
+            combo.removeActionListener(handler);
+            combo.removePropertyChangeListener("model", handler);
+            handler = null;
+            cachedItem = null;
+        }
+
+        private class Handler implements ActionListener, PropertyChangeListener {
+            private void comboSelectionChanged() {
+                Object oldValue = cachedItem;
+                cachedItem = getSelectedItem();
+                firePropertyChange(oldValue, cachedItem);
+            }
+
+            public void actionPerformed(ActionEvent ae) {
+                comboSelectionChanged();
+            }
+
+            public void propertyChange(PropertyChangeEvent pce) {
+                comboSelectionChanged();
+            }
+        }
+    }
+
+    public boolean providesAdapter(Class<?> type, String property) {
+        return JComboBox.class.isAssignableFrom(type) && property.intern() == SELECTED_ITEM_P;
+    }
+
+    public Object createAdapter(Object source, String property) {
+        if (!providesAdapter(source.getClass(), property)) {
+            throw new IllegalArgumentException();
+        }
+
+        return new Adapter((JComboBox)source);
+    }
+
+    public Class<?> getAdapterClass(Class<?> type) {
+        return JList.class.isAssignableFrom(type) ? 
+            JComboBoxAdapterProvider.Adapter.class :
+            null;
+    }
+
+}
diff --git a/src/org/jdesktop/swingbinding/adapters/JListAdapterProvider.java b/src/org/jdesktop/swingbinding/adapters/JListAdapterProvider.java
new file mode 100644
index 0000000..aecbd22
--- /dev/null
+++ b/src/org/jdesktop/swingbinding/adapters/JListAdapterProvider.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.swingbinding.adapters;
+
+import java.beans.*;
+import javax.swing.*;
+import org.jdesktop.beansbinding.ext.BeanAdapterProvider;
+import org.jdesktop.swingbinding.impl.ListBindingManager;
+import javax.swing.event.*;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * @author Shannon Hickey
+ */
+public final class JListAdapterProvider implements BeanAdapterProvider {
+
+    private static final String SELECTED_ELEMENT_P = "selectedElement";
+    private static final String SELECTED_ELEMENTS_P = "selectedElements";
+    private static final String SELECTED_ELEMENT_IA_P = SELECTED_ELEMENT_P + "_IGNORE_ADJUSTING";
+    private static final String SELECTED_ELEMENTS_IA_P = SELECTED_ELEMENTS_P + "_IGNORE_ADJUSTING";
+
+    public final class Adapter extends BeanAdapterBase {
+        private JList list;
+        private Handler handler;
+        private Object cachedElementOrElements;
+
+        private Adapter(JList list, String property) {
+            super(property);
+            this.list = list;
+        }
+
+        private boolean isPlural() {
+            return property == SELECTED_ELEMENTS_P || property == SELECTED_ELEMENTS_IA_P;
+        }
+
+        public Object getSelectedElement() {
+            return JListAdapterProvider.getSelectedElement(list);
+        }
+        
+        public Object getSelectedElement_IGNORE_ADJUSTING() {
+            return getSelectedElement();
+        }
+
+        public List<Object> getSelectedElements() {
+            return JListAdapterProvider.getSelectedElements(list);
+        }
+
+        public List<Object> getSelectedElements_IGNORE_ADJUSTING() {
+            return getSelectedElements();
+        }
+        
+        protected void listeningStarted() {
+            handler = new Handler();
+            cachedElementOrElements = isPlural() ?
+                getSelectedElements() : getSelectedElement();
+            list.addPropertyChangeListener("model", handler);
+            list.addPropertyChangeListener("selectionModel", handler);
+            list.getSelectionModel().addListSelectionListener(handler);
+        }
+        
+        protected void listeningStopped() {
+            list.getSelectionModel().removeListSelectionListener(handler);
+            list.removePropertyChangeListener("model", handler);
+            list.removePropertyChangeListener("selectionModel", handler);
+            cachedElementOrElements = null;
+            handler = null;
+        }
+
+        private class Handler implements ListSelectionListener, PropertyChangeListener {
+            private void listSelectionChanged() {
+                Object oldElementOrElements = cachedElementOrElements;
+                cachedElementOrElements = isPlural() ?
+                    getSelectedElements() : getSelectedElement();
+                firePropertyChange(oldElementOrElements, cachedElementOrElements);
+            }
+
+            public void valueChanged(ListSelectionEvent e) {
+                if ((property == SELECTED_ELEMENT_IA_P || property == SELECTED_ELEMENTS_IA_P)
+                        && e.getValueIsAdjusting()) {
+
+                    return;
+                }
+
+                listSelectionChanged();
+            }
+            
+            public void propertyChange(PropertyChangeEvent pce) {
+                String propertyName = pce.getPropertyName();
+
+                if (propertyName == "selectionModel") {
+                    ((ListSelectionModel)pce.getOldValue()).removeListSelectionListener(handler);
+                    ((ListSelectionModel)pce.getNewValue()).addListSelectionListener(handler);
+                    listSelectionChanged();
+                } else if (propertyName == "model") {
+                    listSelectionChanged();
+                }
+            }
+        }
+    }
+
+    private static List<Object> getSelectedElements(JList list) {
+        assert list != null;
+
+        ListSelectionModel selectionModel = list.getSelectionModel();
+        int min = selectionModel.getMinSelectionIndex();
+        int max = selectionModel.getMaxSelectionIndex();
+
+        List<Object> newSelection;
+
+        if (min < 0 || max < 0) {
+            return new ArrayList<Object>(0);
+        }
+        
+        ArrayList<Object> elements = new ArrayList<Object>(max - min + 1);
+
+        for (int i = min; i <= max; i++) {
+            if (selectionModel.isSelectedIndex(i)) {
+                elements.add(getElement(list, i));
+            }
+        }
+
+        return elements;
+    }
+    
+    private static Object getSelectedElement(JList list) {
+        assert list != null;
+
+        // PENDING(shannonh) - more cases to consider
+        int index = list.getSelectionModel().getLeadSelectionIndex();
+        index = list.getSelectionModel().isSelectedIndex(index) ?
+            index : list.getSelectionModel().getMinSelectionIndex();
+
+        if (index == -1) {
+            return null;
+        }
+
+        return getElement(list, index);
+    }
+
+    private static Object getElement(JList list, int index) {
+        ListModel model = list.getModel();
+        return model instanceof ListBindingManager ? ((ListBindingManager)model).getElement(index)
+                                                   : model.getElementAt(index);
+    }
+    
+    public boolean providesAdapter(Class<?> type, String property) {
+        if (!JList.class.isAssignableFrom(type)) {
+            return false;
+        }
+
+        property = property.intern();
+
+        return property == SELECTED_ELEMENT_P ||
+               property == SELECTED_ELEMENT_IA_P ||
+               property == SELECTED_ELEMENTS_P ||
+               property == SELECTED_ELEMENTS_IA_P;
+                 
+    }
+    
+    public Object createAdapter(Object source, String property) {
+        if (!providesAdapter(source.getClass(), property)) {
+            throw new IllegalArgumentException();
+        }
+        
+        return new Adapter((JList)source, property);
+    }
+    
+    public Class<?> getAdapterClass(Class<?> type) {
+        return JList.class.isAssignableFrom(type) ? 
+            JListAdapterProvider.Adapter.class :
+            null;
+    }
+    
+}
diff --git a/src/org/jdesktop/swingbinding/adapters/JSliderAdapterProvider.java b/src/org/jdesktop/swingbinding/adapters/JSliderAdapterProvider.java
new file mode 100644
index 0000000..090ac2e
--- /dev/null
+++ b/src/org/jdesktop/swingbinding/adapters/JSliderAdapterProvider.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.swingbinding.adapters;
+
+import javax.swing.*;
+import javax.swing.event.*;
+import java.beans.*;
+import org.jdesktop.beansbinding.ext.BeanAdapterProvider;
+
+/**
+ * @author Shannon Hickey
+ */
+public final class JSliderAdapterProvider implements BeanAdapterProvider {
+
+    private static final String PROPERTY_BASE = "value";
+    private static final String IGNORE_ADJUSTING = PROPERTY_BASE + "_IGNORE_ADJUSTING";
+
+    public static final class Adapter extends BeanAdapterBase {
+        private JSlider slider;
+        private Handler handler;
+        private int cachedValue;
+
+        private Adapter(JSlider slider, String property) {
+            super(property);
+            this.slider = slider;
+        }
+
+        public int getValue() {
+            return slider.getValue();
+        }
+
+        public int getValue_IGNORE_ADJUSTING() {
+            return getValue();
+        }
+
+        public void setValue(int value) {
+            slider.setValue(value);
+        }
+        
+        public void setValue_IGNORE_ADJUSTING(int value) {
+            setValue(value);
+        }
+
+        protected void listeningStarted() {
+            handler = new Handler();
+            cachedValue = getValue();
+            slider.addChangeListener(handler);
+            slider.addPropertyChangeListener("model", handler);
+        }
+
+        protected void listeningStopped() {
+            slider.removeChangeListener(handler);
+            slider.removePropertyChangeListener("model", handler);
+            handler = null;
+        }
+        
+        private class Handler implements ChangeListener, PropertyChangeListener {
+            private void sliderValueChanged() {
+                int oldValue = cachedValue;
+                cachedValue = getValue();
+                firePropertyChange(oldValue, cachedValue);
+            }
+
+            public void stateChanged(ChangeEvent ce) {
+                if (property == IGNORE_ADJUSTING && slider.getValueIsAdjusting()) {
+                    return;
+                }
+
+                sliderValueChanged();
+            }
+
+            public void propertyChange(PropertyChangeEvent pe) {
+                sliderValueChanged();
+            }
+        }
+    }
+
+    public boolean providesAdapter(Class<?> type, String property) {
+        if (!JSlider.class.isAssignableFrom(type)) {
+            return false;
+        }
+
+        property = property.intern();
+        
+        return property == PROPERTY_BASE ||
+               property == IGNORE_ADJUSTING;
+    }
+
+    public Object createAdapter(Object source, String property) {
+        if (!providesAdapter(source.getClass(), property)) {
+            throw new IllegalArgumentException();
+        }
+
+        return new Adapter((JSlider)source, property);
+    }
+
+    public Class<?> getAdapterClass(Class<?> type) {
+        return JSlider.class.isAssignableFrom(type) ?
+            JSliderAdapterProvider.Adapter.class :
+            null;
+    }
+
+}
diff --git a/src/org/jdesktop/swingbinding/adapters/JSpinnerAdapterProvider.java b/src/org/jdesktop/swingbinding/adapters/JSpinnerAdapterProvider.java
new file mode 100644
index 0000000..90c445b
--- /dev/null
+++ b/src/org/jdesktop/swingbinding/adapters/JSpinnerAdapterProvider.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.swingbinding.adapters;
+
+import javax.swing.*;
+import javax.swing.event.*;
+import java.beans.*;
+import org.jdesktop.beansbinding.ext.BeanAdapterProvider;
+
+/**
+ * @author Shannon Hickey
+ */
+public final class JSpinnerAdapterProvider implements BeanAdapterProvider {
+
+    private static final String VALUE_P = "value";
+
+    public static final class Adapter extends BeanAdapterBase {
+        private JSpinner spinner;
+        private Handler handler;
+        private Object cachedValue;
+
+        private Adapter(JSpinner spinner) {
+            super(VALUE_P);
+            this.spinner = spinner;
+        }
+
+        public Object getValue() {
+            return spinner.getValue();
+        }
+
+        public void setValue(Object value) {
+            spinner.setValue(value);
+        }
+
+        protected void listeningStarted() {
+            handler = new Handler();
+            cachedValue = getValue();
+            spinner.addChangeListener(handler);
+            spinner.addPropertyChangeListener("model", handler);
+        }
+
+        protected void listeningStopped() {
+            spinner.removeChangeListener(handler);
+            spinner.removePropertyChangeListener("model", handler);
+            handler = null;
+        }
+        
+        private class Handler implements ChangeListener, PropertyChangeListener {
+            private void spinnerValueChanged() {
+                Object oldValue = cachedValue;
+                cachedValue = getValue();
+                firePropertyChange(oldValue, cachedValue);
+            }
+
+            public void stateChanged(ChangeEvent ce) {
+                spinnerValueChanged();
+            }
+
+            public void propertyChange(PropertyChangeEvent pe) {
+                spinnerValueChanged();
+            }
+        }
+    }
+
+    public boolean providesAdapter(Class<?> type, String property) {
+        return JSpinner.class.isAssignableFrom(type) && property == VALUE_P;
+    }
+
+    public Object createAdapter(Object source, String property) {
+        if (!providesAdapter(source.getClass(), property)) {
+            throw new IllegalArgumentException();
+        }
+
+        return new Adapter((JSpinner)source);
+    }
+
+    public Class<?> getAdapterClass(Class<?> type) {
+        return JSpinner.class.isAssignableFrom(type) ?
+            JSpinnerAdapterProvider.Adapter.class :
+            null;
+    }
+
+}
diff --git a/src/org/jdesktop/swingbinding/adapters/JTableAdapterProvider.java b/src/org/jdesktop/swingbinding/adapters/JTableAdapterProvider.java
new file mode 100644
index 0000000..47cfcb0
--- /dev/null
+++ b/src/org/jdesktop/swingbinding/adapters/JTableAdapterProvider.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.swingbinding.adapters;
+
+import java.beans.*;
+import javax.swing.*;
+import org.jdesktop.beansbinding.ext.BeanAdapterProvider;
+import org.jdesktop.swingbinding.impl.ListBindingManager;
+import javax.swing.event.*;
+import javax.swing.table.*;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ArrayList;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * @author Shannon Hickey
+ */
+public final class JTableAdapterProvider implements BeanAdapterProvider {
+
+    private static final String SELECTED_ELEMENT_P = "selectedElement";
+    private static final String SELECTED_ELEMENTS_P = "selectedElements";
+    private static final String SELECTED_ELEMENT_IA_P = SELECTED_ELEMENT_P + "_IGNORE_ADJUSTING";
+    private static final String SELECTED_ELEMENTS_IA_P = SELECTED_ELEMENTS_P + "_IGNORE_ADJUSTING";
+
+    private static boolean IS_JAVA_15 =
+        System.getProperty("java.version").startsWith("1.5");
+
+    public final class Adapter extends BeanAdapterBase {
+        private JTable table;
+        private Handler handler;
+        private Object cachedElementOrElements;
+
+        private Adapter(JTable table, String property) {
+            super(property);
+            this.table = table;
+        }
+
+        private boolean isPlural() {
+            return property == SELECTED_ELEMENTS_P || property == SELECTED_ELEMENTS_IA_P;
+        }
+        
+        public Object getSelectedElement() {
+            return JTableAdapterProvider.getSelectedElement(table);
+        }
+        
+        public Object getSelectedElement_IGNORE_ADJUSTING() {
+            return getSelectedElement();
+        }
+
+        public List<Object> getSelectedElements() {
+            return JTableAdapterProvider.getSelectedElements(table);
+        }
+
+        public List<Object> getSelectedElements_IGNORE_ADJUSTING() {
+            return getSelectedElements();
+        }
+
+        protected void listeningStarted() {
+            handler = new Handler();
+            cachedElementOrElements = isPlural() ?
+                getSelectedElements() : JTableAdapterProvider.getSelectedElement(table);
+            table.addPropertyChangeListener("selectionModel", handler);
+            table.getSelectionModel().addListSelectionListener(handler);
+        }
+        
+        protected void listeningStopped() {
+            table.getSelectionModel().removeListSelectionListener(handler);
+            table.removePropertyChangeListener("selectionModel", handler);
+            cachedElementOrElements = null;
+            handler = null;
+        }
+
+        private class Handler implements ListSelectionListener, PropertyChangeListener {
+            private void tableSelectionChanged() {
+                Object oldElementOrElements = cachedElementOrElements;
+                cachedElementOrElements = getSelectedElements();
+                firePropertyChange(oldElementOrElements, cachedElementOrElements);
+            }
+
+            public void valueChanged(ListSelectionEvent e) {
+                if ((property == SELECTED_ELEMENT_IA_P || property == SELECTED_ELEMENTS_IA_P)
+                        && e.getValueIsAdjusting()) {
+
+                    return;
+                }
+
+                tableSelectionChanged();
+            }
+            
+            public void propertyChange(PropertyChangeEvent pce) {
+                ((ListSelectionModel)pce.getOldValue()).removeListSelectionListener(handler);
+                ((ListSelectionModel)pce.getNewValue()).addListSelectionListener(handler);
+                tableSelectionChanged();
+            }
+        }
+    }
+
+    private static int viewToModel(JTable table, int index) {
+        // deal with sorting & filtering in 6.0 and up
+        if (!IS_JAVA_15) {
+            try {
+                java.lang.reflect.Method m = table.getClass().getMethod("convertRowIndexToModel", int.class);
+                index = (Integer)m.invoke(table, index);
+            } catch (NoSuchMethodException nsme) {
+                throw new AssertionError(nsme);
+            } catch (IllegalAccessException iae) {
+                throw new AssertionError(iae);
+            } catch (InvocationTargetException ite) {
+                Throwable cause = ite.getCause();
+                if (cause instanceof Error) {
+                    throw (Error)cause;
+                } else {
+                    throw new RuntimeException(cause);
+                }
+            }
+        }
+
+        return index;
+    }
+
+    private static int modelToView(JTable table, int index) {
+        // deal with sorting & filtering in 6.0 and up
+        if (!IS_JAVA_15) {
+            try {
+                java.lang.reflect.Method m = table.getClass().getMethod("convertRowIndexToView", int.class);
+                index = (Integer)m.invoke(table, index);
+            } catch (NoSuchMethodException nsme) {
+                throw new AssertionError(nsme);
+            } catch (IllegalAccessException iae) {
+                throw new AssertionError(iae);
+            } catch (InvocationTargetException ite) {
+                Throwable cause = ite.getCause();
+                if (cause instanceof Error) {
+                    throw (Error)cause;
+                } else {
+                    throw new RuntimeException(cause);
+                }
+            }
+        }
+
+        return index;
+    }
+    
+    private static List<Object> getSelectedElements(JTable table) {
+        assert table != null;
+
+        ListSelectionModel selectionModel = table.getSelectionModel();
+        int min = selectionModel.getMinSelectionIndex();
+        int max = selectionModel.getMaxSelectionIndex();
+
+        List<Object> newSelection;
+
+        if (min < 0 || max < 0) {
+            return new ArrayList<Object>(0);
+        }
+        
+        ArrayList<Object> elements = new ArrayList<Object>(max - min + 1);
+
+        for (int i = min; i <= max; i++) {
+            if (selectionModel.isSelectedIndex(i)) {
+                elements.add(getElement(table, i));
+            }
+        }
+        
+        return elements;
+    }
+
+    private static Object getSelectedElement(JTable table) {
+        assert table != null;
+
+        // PENDING(shannonh) - more cases to consider
+        int index = table.getSelectionModel().getLeadSelectionIndex();
+        index = table.getSelectionModel().isSelectedIndex(index) ?
+            index : table.getSelectionModel().getMinSelectionIndex();
+        
+        if (index == -1) {
+            return null;
+        }
+
+        return getElement(table, index);
+    }
+
+    private static Object getElement(JTable table, int index) {
+        index = viewToModel(table, index);
+        
+        TableModel model = table.getModel();
+        if (model instanceof ListBindingManager) {
+            return ((ListBindingManager)model).getElement(index);
+        } else {
+            int columnCount = model.getColumnCount();
+            // PENDING(shannonh) - need to support editing values in this map!
+            HashMap map = new HashMap(columnCount);
+            for (int i = 0; i < columnCount; i++) {
+                map.put("column" + i, model.getValueAt(index, i));
+            }
+            return map;
+        }
+    }
+
+    public boolean providesAdapter(Class<?> type, String property) {
+        if (!JTable.class.isAssignableFrom(type)) {
+            return false;
+        }
+
+        property = property.intern();
+
+        return property == SELECTED_ELEMENT_P ||
+               property == SELECTED_ELEMENT_IA_P ||
+               property == SELECTED_ELEMENTS_P ||
+               property == SELECTED_ELEMENTS_IA_P;
+                 
+    }
+
+    public Object createAdapter(Object source, String property) {
+        if (!providesAdapter(source.getClass(), property)) {
+            throw new IllegalArgumentException();
+        }
+        
+        return new Adapter((JTable)source, property);
+    }
+    
+    public Class<?> getAdapterClass(Class<?> type) {
+        return JTable.class.isAssignableFrom(type) ?
+            JTableAdapterProvider.Adapter.class :
+            null;
+    }
+    
+}
diff --git a/src/org/jdesktop/swingbinding/adapters/JTextComponentAdapterProvider.java b/src/org/jdesktop/swingbinding/adapters/JTextComponentAdapterProvider.java
new file mode 100644
index 0000000..5e13579
--- /dev/null
+++ b/src/org/jdesktop/swingbinding/adapters/JTextComponentAdapterProvider.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.swingbinding.adapters;
+
+import java.beans.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import java.awt.event.*;
+import javax.swing.text.*;
+import org.jdesktop.beansbinding.ext.BeanAdapterProvider;
+
+/**
+ * @author Shannon Hickey
+ */
+public final class JTextComponentAdapterProvider implements BeanAdapterProvider {
+
+    private static final String PROPERTY_BASE = "text";
+    private static final String ON_ACTION_OR_FOCUS_LOST = PROPERTY_BASE + "_ON_ACTION_OR_FOCUS_LOST";
+    private static final String ON_FOCUS_LOST = PROPERTY_BASE + "_ON_FOCUS_LOST";
+
+    public final class Adapter extends BeanAdapterBase {
+        private JTextComponent component;
+        private Document document;
+        private boolean inDocumentListener;
+        private boolean installedFilter;
+        private String cachedText;
+        private Handler handler;
+
+        private Adapter(JTextComponent component, String property) {
+            super(property);
+            this.component = component;
+        }
+
+        public String getText() {
+            return component.getText();
+        }
+
+        public String getText_ON_ACTION_OR_FOCUS_LOST() {
+            return getText();
+        }
+
+        public String getText_ON_FOCUS_LOST() {
+            return getText();
+        }
+
+        public void setText(String text) {
+            component.setText(text);
+            component.setCaretPosition(0);
+            cachedText = text;
+        }
+
+        public void setText_ON_ACTION_OR_FOCUS_LOST(String text) {
+            setText(text);
+        }
+
+        public void setText_ON_FOCUS_LOST(String text) {
+            setText(text);
+        }
+        
+        protected void listeningStarted() {
+            cachedText = component.getText();
+            handler = new Handler();
+            component.addPropertyChangeListener("document", handler);
+
+            if (property != PROPERTY_BASE) {
+                component.addFocusListener(handler);
+            }
+
+            if (property == ON_ACTION_OR_FOCUS_LOST && component instanceof JTextField) {
+                ((JTextField)component).addActionListener(handler);
+            }
+
+            document = component.getDocument();
+            installDocumentListener();
+            
+        }
+        
+        protected void listeningStopped() {
+            cachedText = null;
+            component.removePropertyChangeListener("document", handler);
+            
+            if (property != PROPERTY_BASE) {
+                component.removeFocusListener(handler);
+            }
+            
+            if (property == ON_ACTION_OR_FOCUS_LOST && (component instanceof JTextField)) {
+                ((JTextField)component).removeActionListener(handler);
+            }
+
+            uninstallDocumentListener();
+            document = null;
+            handler = null;
+        }
+
+        private void installDocumentListener() {
+            if (property != PROPERTY_BASE) {
+                return;
+            }
+
+            boolean useDocumentFilter = !(component instanceof JFormattedTextField);
+            
+            if (useDocumentFilter && (document instanceof AbstractDocument) &&
+                    ((AbstractDocument)document).getDocumentFilter() == null) {
+                ((AbstractDocument)document).setDocumentFilter(handler);
+                installedFilter = true;
+            } else {
+                document.addDocumentListener(handler);
+                installedFilter = false;
+            }
+        }
+        
+        private void uninstallDocumentListener() {
+            if (property != PROPERTY_BASE) {
+                return;
+            }
+
+            if (installedFilter) {
+                AbstractDocument ad = (AbstractDocument)document;
+                if (ad.getDocumentFilter() == handler) {
+                    ad.setDocumentFilter(null);
+                }
+            } else {
+                document.removeDocumentListener(handler);
+            }
+        }
+
+        private class Handler extends DocumentFilter implements ActionListener, DocumentListener,
+                FocusListener, PropertyChangeListener {
+
+            private void updateText() {
+                Object oldText = cachedText;
+                cachedText = getText();
+                firePropertyChange(oldText, cachedText);
+            }
+
+            private void documentTextChanged() {
+                try {
+                    inDocumentListener = true;
+                    textChanged();
+                } finally {
+                    inDocumentListener = false;
+                }
+            }
+            
+            private void textChanged() {
+                updateText();
+            }
+            
+            public void propertyChange(PropertyChangeEvent pce) {
+                uninstallDocumentListener();
+                document = component.getDocument();
+                installDocumentListener();
+                updateText();
+            }
+            
+            public void actionPerformed(ActionEvent e) {
+                updateText();
+            }
+
+            public void focusLost(FocusEvent e) {
+                if (!e.isTemporary()) {
+                    updateText();
+                }
+            }
+            
+            public void insertUpdate(DocumentEvent e) {
+                documentTextChanged();
+            }
+            
+            public void removeUpdate(DocumentEvent e) {
+                documentTextChanged();
+            }
+            
+            public void replace(DocumentFilter.FilterBypass fb, int offset,
+                    int length, String text, AttributeSet attrs)
+                    throws BadLocationException {
+                
+                super.replace(fb, offset, length, text, attrs);
+                textChanged();
+            }
+            
+            public void insertString(DocumentFilter.FilterBypass fb, int offset,
+                                     String string, AttributeSet attr) throws BadLocationException {
+
+                super.insertString(fb, offset, string, attr);
+                textChanged();
+            }
+            
+            public void remove(DocumentFilter.FilterBypass fb, int offset, int length) throws BadLocationException {
+                super.remove(fb, offset, length);
+                textChanged();
+            }
+
+            public void focusGained(FocusEvent e) {}
+            public void changedUpdate(DocumentEvent e) {}
+        }
+        
+    }
+    
+    public boolean providesAdapter(Class<?> type, String property) {
+        if (!JTextComponent.class.isAssignableFrom(type)) {
+            return false;
+        }
+
+        property = property.intern();
+
+        return property == PROPERTY_BASE ||
+               property == ON_ACTION_OR_FOCUS_LOST ||
+               property == ON_FOCUS_LOST;
+                 
+    }
+    
+    public Object createAdapter(Object source, String property) {
+        if (!providesAdapter(source.getClass(), property)) {
+            throw new IllegalArgumentException();
+        }
+        
+        return new Adapter((JTextComponent)source, property);
+    }
+    
+    public Class<?> getAdapterClass(Class<?> type) {
+        return JTextComponent.class.isAssignableFrom(type) ?
+            JTextComponentAdapterProvider.Adapter.class :
+            null;
+    }
+    
+}
diff --git a/src/org/jdesktop/swingbinding/adapters/package.html b/src/org/jdesktop/swingbinding/adapters/package.html
new file mode 100644
index 0000000..3dd429f
--- /dev/null
+++ b/src/org/jdesktop/swingbinding/adapters/package.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<!--
+ Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ subject to license terms.
+-->
+
+<html>
+    <head>
+        <title></title>
+    </head>
+    <body>
+        <p>
+            Private implementation details for Swing binding. Contains adapters
+            for certain Swing classes to adapt existing properties of
+            interest to the Beans specification, and provide additional
+            synthetic properties. These classes are public as required to enable the
+            properties to be inspected at runtime. Nonetheless, they are an
+            implementation detail of the Swing binding support. Do not access them
+            directly as they may change or go away at any time.
+        </p>
+        <p>
+            For the public list of interesting Swing properties, see the
+            <a href="../package-summary.html">swingbinding</a> package level
+            documentation.
+        </p>
+    </body>
+</html>
diff --git a/src/org/jdesktop/swingbinding/impl/AbstractColumnBinding.java b/src/org/jdesktop/swingbinding/impl/AbstractColumnBinding.java
new file mode 100644
index 0000000..28b6b0e
--- /dev/null
+++ b/src/org/jdesktop/swingbinding/impl/AbstractColumnBinding.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.swingbinding.impl;
+
+import org.jdesktop.beansbinding.*;
+
+/**
+ * @author Shannon Hickey
+ */
+public abstract class AbstractColumnBinding extends Binding {
+
+    private int column;
+
+    public AbstractColumnBinding(int column, Property columnSource, Property columnTarget, String name) {
+        super(null, columnSource, null, columnTarget, name);
+        this.column = column;
+        setManaged(true);
+    }
+
+    public final int getColumn() {
+        return column;
+    }
+
+    protected final void setColumn(int column) {
+        this.column = column;
+    }
+
+    public void bindImpl() {}
+
+    public void unbindImpl() {}
+
+}
diff --git a/src/org/jdesktop/swingbinding/impl/ListBindingManager.java b/src/org/jdesktop/swingbinding/impl/ListBindingManager.java
new file mode 100644
index 0000000..44e8100
--- /dev/null
+++ b/src/org/jdesktop/swingbinding/impl/ListBindingManager.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2006-2007 Sun Microsystems, Inc. All rights reserved. Use is
+ * subject to license terms.
+ */
+
+package org.jdesktop.swingbinding.impl;
+
+import org.jdesktop.observablecollections.ObservableList;
+import org.jdesktop.observablecollections.ObservableListListener;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.jdesktop.beansbinding.*;
+
+/**
+ * @author sky
+ * @author Shannon Hickey
+ */
+public abstract class ListBindingManager implements ObservableListListener {
+    private AbstractColumnBinding[] bindings;
+    private ReusableBinding reusableBinding;
+    private List<?> elements;
+    private List<ColumnDescriptionManager> managers;
+
+    public ListBindingManager() {
+        bindings = getColBindings();
+    }
+
+    private List<ColumnDescriptionManager> createManagers(AbstractColumnBinding[] bindings) {
+        List<ColumnDescriptionManager> managers = new ArrayList<ColumnDescriptionManager>(bindings.length);
+
+        for (AbstractColumnBinding binding : bindings) {
+            managers.add(new ColumnDescriptionManager(binding));
+        }
+
+        return managers;
+    }
+
+    protected abstract AbstractColumnBinding[] getColBindings();
+
+    public void setElements(List<?> elements, boolean sendAllChanged) {
+        if (this.elements != null) {
+            if (this.elements instanceof ObservableList) {
+                ((ObservableList)this.elements).removeObservableListListener(this);
+            }
+
+            if (managers != null) {
+                for (ColumnDescriptionManager manager : managers) {
+                    manager.stopListening();
+                }
+            }
+        }
+
+        managers = null;
+        reusableBinding = null;
+        this.elements = (elements == null) ? Collections.emptyList() : elements;
+
+        boolean addListeners = false;
+
+        if (elements instanceof ObservableList) {
+            ((ObservableList)elements).addObservableListListener(this);
+            addListeners = !((ObservableList)elements).supportsElementPropertyChanged();
+        } else if (elements != null) {
+            addListeners = true;
+        }
+
+        if (bindings.length != 0) {
+            reusableBinding = new ReusableBinding(bindings[0]);
+        }
+
+        if (addListeners) {
+            managers = createManagers(getColBindings());
+            for (ColumnDescriptionManager manager : managers) {
+                manager.startListening();
+            }
+        }
+
+        if (sendAllChanged) {
+            allChanged();
+        }
+    }
+    
+    public final Object getElement(int index) {
+        return elements.get(index);
+    }
+
+    public final List<?> getElements() {
+        return elements;
+    }
+    
+    public final int size() {
+        return (elements == null) ? 0 : elements.size();
+    }
+    
+    public final Object valueAt(int row, int column) {
+        if (managers != null) {
+            // Make sure the necessary listeners have been registered
+            for (ColumnDescriptionManager manager : managers) {
+                manager.validateBinding(row);
+            }
+        }
+
+        reusableBinding.setBaseAndSource(bindings[column], elements.get(row));
+        Binding.ValueResult result = reusableBinding.getSourceValueForTarget();
+        return result.failed() ? null : result.getValue();
+    }
+
+    public final int columnCount() {
+        return bindings.length;
+    }
+
+    public final void listElementsAdded(ObservableList list, int index, int length) {
+        if (length == 0) {
+            return;
+        }
+
+        if (managers != null) {
+            for (ColumnDescriptionManager manager : managers) {
+                manager.add(index, length);
+            }
+        }
+
+        added(index, length);
+    }
+    
+    public final void listElementsRemoved(ObservableList list, int index, List elements) {
+        if (elements.size() == 0) {
+            return;
+        }
+
+        if (managers != null) {
+            for (ColumnDescriptionManager manager : managers) {
+                manager.remove(index, elements.size());
+            }
+        }
+
+        removed(index, elements.size());
+    }
+    
+    public final void listElementReplaced(ObservableList list, int index, Object oldElement) {
+        if (managers != null) {
+            for (ColumnDescriptionManager manager : managers) {
+                manager.replaced(index);
+            }
+        }
+
+        changed(index);
+    }
+    
+    public final void listElementPropertyChanged(ObservableList list, int index) {
+        changed(index);
+    }
+    
+    protected abstract void allChanged();
+
+    protected abstract void valueChanged(int row, int column);
+
+    protected abstract void added(int index, int length);
+
+    protected abstract void removed(int index, int length);
+
+    protected abstract void changed(int row);
+
+    private final class ColumnDescriptionManager {
+        private final AbstractColumnBinding columnBinding;
+        private List<EntryWrapper> wrappers;
+
+        ColumnDescriptionManager(AbstractColumnBinding columnBinding) {
+            this.columnBinding = columnBinding;
+        }
+
+        public void startListening() {
+            int size = elements.size();
+            wrappers = new ArrayList<EntryWrapper>(size);
+            for (int i = 0; i < size; i++) {
+                wrappers.add(null);
+            }
+        }
+
+        public void stopListening() {
+            for (EntryWrapper wrapper : wrappers) {
+                if (wrapper != null) {
+                    wrapper.stopListening();
+                }
+            }
+
+            wrappers = null;
+        }
+
+        public void validateBinding(int row) {
+            if (wrappers.get(row) == null) {
+                EntryWrapper wrapper = new EntryWrapper(getElement(row));
+                wrappers.set(row, wrapper);
+            }
+        }
+
+        void wrapperChanged(EntryWrapper wrapper) {
+            int row = wrappers.indexOf(wrapper);
+            ListBindingManager.this.valueChanged(row, columnBinding.getColumn());
+        }
+
+        private void add(int index, int length) {
+            for (int i = 0; i < length; i++) {
+                wrappers.add(index, null);
+            }
+        }
+
+        private void remove(int index, int length) {
+            while (length-- > 0) {
+                EntryWrapper wrapper = wrappers.remove(index);
+                if (wrapper != null) {
+                    wrapper.stopListening();
+                }
+            }
+        }
+
+        private void replaced(int index) {
+            EntryWrapper wrapper = wrappers.get(index);
+            if (wrapper != null) {
+                wrapper.stopListening();
+            }
+            wrappers.set(index, null);
+        }
+
+        private final class EntryWrapper implements PropertyStateListener {
+            private Object source;
+
+            EntryWrapper(Object source) {
+                this.source = source;
+                columnBinding.getSourceProperty().addPropertyStateListener(source, this);
+            }
+            
+            public void stopListening() {
+                columnBinding.getSourceProperty().removePropertyStateListener(source, this);
+                source = null;
+            }
+
+            public void propertyStateChanged(PropertyStateEvent pse) {
+                if (pse.getValueChanged()) {
+                    wrapperChanged(this);
+                }
+            }
+        }
+    }
+
+    private final class ReusableBinding extends Binding {
+        public ReusableBinding(AbstractColumnBinding base) {
+            super(null, base.getSourceProperty(), null, base.getTargetProperty(), null);
+        }
+
+        public void setBaseAndSource(AbstractColumnBinding base, Object source) {
+            setSourceProperty(base.getSourceProperty());
+            setTargetProperty(base.getTargetProperty());
+            setSourceObject(source);
+            setConverter(base.getConverter());
+            setSourceNullValue(base.getSourceNullValue());
+            if (base.isSourceUnreadableValueSet()) {
+                setSourceUnreadableValue(base.getSourceUnreadableValue());
+            } else {
+                unsetSourceUnreadableValue();
+            }
+        }
+        
+        public final void bindImpl() {}
+        public final void unbindImpl() {}
+
+    }
+
+}
diff --git a/src/org/jdesktop/swingbinding/impl/package.html b/src/org/jdesktop/swingbinding/impl/package.html
new file mode 100644
index 0000000..f8ff3d6
--- /dev/null
+++ b/src/org/jdesktop/swingbinding/impl/package.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<!--
+ Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ subject to license terms.
+-->
+
+<html>
+    <head>
+        <title></title>
+    </head>
+    <body>
+        <p>
+            Private implementation details for Swing binding. These classes
+            are public only to allow sharing between packages containing the
+            Swing binding implementation. Do not use them directly as they
+            may change or go away at any time.
+        </p>
+    </body>
+</html>
diff --git a/src/org/jdesktop/swingbinding/package.html b/src/org/jdesktop/swingbinding/package.html
new file mode 100644
index 0000000..2307f30
--- /dev/null
+++ b/src/org/jdesktop/swingbinding/package.html
@@ -0,0 +1,243 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<!--
+ Copyright (C) 2007 Sun Microsystems, Inc. All rights reserved. Use is
+ subject to license terms.
+-->
+
+<html>
+    <head>
+        <title></title>
+    </head>
+    <body>
+        <p>
+            Provides support for binding to complex Swing components, and
+            documentation on the interesting Swing properties to bind to.
+        </p>
+        <a name="SWING_PROPS"><h3>Interesting Swing Properties</h3></a>
+        <p>
+            Any Swing component property that conforms to the Java Beans specification
+            is an excellent candidate for use by {@code BeanProperty}, {@code ELProperty}
+            and other {@code Property} implementations that resolve properties in a
+            similar manner. In addition, adapters have been pre-registered for a handful
+            of properties that don't correctly conform to the specification
+            (in this case, don't fire property change notificiation), and a handful
+            of synthetic properties, so that they can be used in the same way.
+            The complete list of adapted and synthetic properties is below:
+        </p>
+        <table border="1">
+            <tr>
+                <th align="left">Component</th><th align="left">Property</th><th align="left">Description</th>
+            </tr>
+            <tr>
+                <td><b>{@code AbstractButton}</b></td>
+                <td>{@code "selected"}</td>
+                <td>The selected state of a button.</td>
+            </tr>
+            <tr><td colspan="3">&nbsp;</td></tr>
+            <tr>
+                <td><b>{@code JComboBox}</b></td>
+                <td>{@code "selectedItem"}</td>
+                <td>The selected item of a {@code JComboBox}.</td>
+            </tr>
+            <tr><td colspan="3">&nbsp;</td></tr>
+            <tr>
+                <td><b>{@code JSpinner}</b></td>
+                <td>{@code "value"}</td>
+                <td>The value of a {@code JSpinner}.</td>
+            </tr>
+            <tr><td colspan="3">&nbsp;</td></tr>
+            <tr>
+                <td><b>{@code JSlider}</b></td>
+                <td>{@code "value"}</td>
+                <td>The value of a {@code JSlider}; notifies of all changes.</td>
+            </tr>
+            <tr>
+                <td>&nbsp;</td>
+                <td>{@code "value_IGNORE_ADJUSTING"}</td>
+                <td>Same as {@code "value"} but does not notify of change while the slider is adjusting its value.</td>
+            </tr>
+            <tr><td colspan="3">&nbsp;</td></tr>
+            <tr>
+                <td><b>{@code JList}</b></td>
+                <td>{@code "selectedElement"}<br><font color="red">&nbsp;&nbsp;(Currently read-only)</font></td>
+                <td>The selected element of a {@code JList}; notifies of all changes.
+                    If there is a {@code JListBinding}
+                    with the {@code JList} as the target, the selected element is reported as an element
+                    from the binding's source list. Otherwise, the selected element is reported as an
+                    object from the list's model. If nothing is selected, the property evaluates to
+                    {@code null}.
+                </td>
+            </tr>
+            <tr>
+                <td>&nbsp;</td>
+                <td>{@code "selectedElements"}<br><font color="red">&nbsp;&nbsp;(Currently read-only)</font></td>
+                <td>A list containing the selected elements of a {@code JList}; notifies of all changes.
+                    If there is a {@code JListBinding} with the {@code JList} as the target, the
+                    selected elements are reported as elements from the binding's source list.
+                    Otherwise, the selected elements are reported as objects from the list's
+                    model. If nothing is selected, the property evaluates to an empty list.
+                </td>
+            </tr>
+            <tr>
+                <td>&nbsp;</td>
+                <td>{@code "selectedElement_IGNORE_ADJUSTING"}<br><font color="red">&nbsp;&nbsp;(Currently read-only)</font></td>
+                <td>Same as "selectedElement" but does not notify of change while the list selection
+                    is being updated.
+                </td>
+            </tr>
+            <tr>
+                <td>&nbsp;</td>
+                <td>{@code "selectedElements_IGNORE_ADJUSTING"}<br><font color="red">&nbsp;&nbsp;(Currently read-only)</font></td>
+                <td>Same as "selectedElements" but does not notify of change while the list selection
+                    is being updated.
+                </td>
+            </tr>
+            <tr><td colspan="3">&nbsp;</td></tr>
+            <tr>
+                <td><b>{@code JTable}</b></td>
+                <td>{@code "selectedElement"}<br><font color="red">&nbsp;&nbsp;(Currently read-only)</font></td>
+                <td>The selected element of a {@code JTable}; notifies of all changes.
+                    If there is a {@code JTableBinding}
+                    with the {@code JTable} as the target, the selected element is reported as an element
+                    from the binding's source list. Otherwise, the selected element is reported as a map
+                    where the keys are composed of the string "column" plus the column index and the values
+                    are the model values for that column.
+                    Example: {@code {column0=column0value, column1=column1value, ...}}
+                    If nothing is selected, the property evaluates to {@code null}.
+                </td>
+            </tr>
+            <tr>
+                <td>&nbsp;</td>
+                <td>{@code "selectedElements"}<br><font color="red">&nbsp;&nbsp;(Currently read-only)</font></td>
+                <td>A list containing the selected elements of a {@code JTable};
+                    notifies of all changes.
+                    If there is a {@code JTableBinding} with the {@code JTable} as the target, the
+                    selected elements are reported as elements from the binding's source list.
+                    Otherwise, each selected element is reported as a map where the keys are composed
+                    of the string "column" plus the column index and the values are the model values
+                    for that column.
+                    Example: {@code {column0=column0value, column1=column1value, ...}}
+                    If nothing is selected, the property evaluates to an empty list.
+                </td>
+            </tr>
+            <tr>
+                <td>&nbsp;</td>
+                <td>{@code "selectedElement_IGNORE_ADJUSTING"}<br><font color="red">&nbsp;&nbsp;(Currently read-only)</font></td>
+                <td>Same as "selectedElement" but does notify of change while the table selection
+                    is being updated.
+                </td>
+            </tr>
+            <tr>
+                <td>&nbsp;</td>
+                <td>{@code "selectedElements_IGNORE_ADJUSTING"}<br><font color="red">&nbsp;&nbsp;(Currently read-only)</font></td>
+                <td>Same as "selectedElements" but does not notify of change while the table selection
+                    is being updated.
+                </td>
+            </tr>
+            <tr><td colspan="3">&nbsp;</td></tr>
+            <tr>
+                <td><b>{@code JTextComponent}</b></td>
+                <td>{@code "text"}</td>
+                <td>The text property of a {@code JTextComponent}; notifies of all changes (including typing).</td>
+            </tr>
+            <tr>
+                <td>&nbsp;</td>
+                <td>{@code "text_ON_FOCUS_LOST"}</td>
+                <td>The text property of a {@code JTextComponent}; notifies of change only when focus is lost on the component.
+            </tr>
+            <tr>
+                <td>&nbsp;</td>
+                <td>{@code "text_ON_ACTION_OR_FOCUS_LOST"}</td>
+                <td>The text property of a {@code JTextComponent}; notifies of change only when
+                    the component notifies of {@code actionPerformed} or when focus is lost on the
+                    component.
+                </td>
+            </tr>
+        </table>
+        <a name="SWING_BINDS"><h3>Swing Binding Subclasses</h3></a>
+        <p>
+            This package supports binding to the more complex Swing components by providing
+            custom {@code Binding} subclasses tailored to the needs of these components.
+            These subclasses are:
+        </p>
+        <table border="1">
+            <tr>
+                <th align="left">Binding class</th><th align="left">Description</th>
+            </tr>
+            <tr>
+                <td><b>{@link org.jdesktop.swingbinding.JComboBoxBinding}</b></td>
+                <td>Bind a {@code java.util.List} of items to be used as the
+                    items in a {@code JComboBox}.
+                </td>
+            </tr>
+            <tr>
+                <td><b>{@link org.jdesktop.swingbinding.JListBinding}</b></td>
+                <td>Bind a {@code java.util.List} of elements to be the elements
+                    of a {@code JList}, and specify how the elements are displayed.
+                </td>
+            </tr>
+            <tr>
+                <td><b>{@link org.jdesktop.swingbinding.JTableBinding}</b></td>
+                <td>Bind a {@code java.util.List} of elements to be the elements
+                    of a {@code JTable}, and specify how properties of the elements
+                    are mapped to columns.
+                </td>
+            </tr>
+            <tr>
+                <td colspan="2"><font color="red"><b>&nbsp;&nbsp;more to come...</b></font></td>
+            </tr>
+        </table>
+        <p>
+            Instances of these classes are obtained by invoking the static {@code create}
+            methods provided by the {@link org.jdesktop.swingbinding.SwingBindings} class.
+        </p>
+        <h3>Examples</h3>
+        <p>
+            Bind a list of {@code Person} beans to a {@code JTable} and have it show
+            the {@code "firstName"} property of each bean in column 0 and the {@code "lastName"}
+            property in column 1:
+        </p>
+        <pre><code>
+    // create the person list
+    List<Person> people = createPersonList();
+            
+    // create the binding from list to table
+    JTableBinding tb = SwingBindings.createJTableBinding(READ, people, table);
+
+    // configure how the properties map to columns
+    tb.addColumnBinding(BeanProperty.create("firstName"));
+    tb.addColumnBinding(BeanProperty.create("lastName"));
+
+    // realize the binding
+    tb.bind();
+        </code></pre>
+        <p>
+            Given the binding above, create a second set of bindings that bind
+            the {@code "firstName"} property of the table's selected element to one
+            {@code JTextField} and the {@code "lastName"} property of the selected
+            element to another. Furthermore, specify that we want the text fields to
+            report change (and therefore have their values committed back to the
+            selected element) only on when focus is lost from the field:
+        </p>
+        <pre><code>
+    // create properties representing the selected person's first name and last name
+    Property selectedFirstP = BeanProperty.create("selectedElement.firstName");
+    Property selectedLastP = BeanProperty.create("selectedElement.lastName");
+
+    // create a property representing a text field's text with change reported only on focus lost
+    Property textP = BeanProperty.create("text_ON_FOCUS_LOST");
+
+    // bind the selected first name and last name to the two text fields
+    Binding b1 = Bindings.createAutoBinding(READ_WRITE, table, selectedFirstP, field1, textP);
+    Binding b2 = Bindings.createAutoBinding(READ_WRITE, table, selectedLastP, field2, textP);
+    
+    // realize the bindings
+    b1.bind();
+    b2.bind();
+        </code></pre>
+        <p>
+            What you've seen above is actually the simple building blocks of a typical master-detail application.
+        </p>
+    </body>
+</html>


hooks/post-receive
-- 
libbeansbinding-java packaging



More information about the pkg-java-commits mailing list