blob: 28812e85eb6e9f81d99dacb2013a55b4b4fb02c1 [file] [log] [blame]
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>XML Expression Evaluation Proposal</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<link rel="stylesheet" href="http://dev.eclipse.org/default_style.css"
type="text/css" />
</head>
<body style="background-color: rgb(255, 255, 255); color: rgb(0, 0, 0)">
<table border="0" cellspacing="5" cellpadding="2" width="100%">
<colgroup>
<col valign="top" width="2%" />
<col valign="top" width="98%" />
</colgroup>
<tbody>
<tr>
<td align="center" valign="top" colspan="2" bgcolor="#0080c0"
width="100%"><b><font face="Arial,Helvetica" color="#ffffff"
size="5">Request for Comments</font></b></td>
</tr>
</tbody>
</table>
<h2 align="center">Expression Evaluation for plugin.xml files</h2>
This proposal outlines a unified solution how to evaluate expressions in
plugin.xml files. The effort and the document is a spin-off of the
<a href="../refactoring/participants.html">refactoring participant</a>
work for which the presented solution got implemented. Feedback is
strongly encouraged and may be provided on the ?? mailing list.
<p>Last modified: December 1, 2003</p>
<h3>Motivation</h3>
<p>One of JDT's plan item is to provide support for other plug-ins to
participate in operations like refactoring (e.g. rename, move, delete,
...), and searching (e.g search for reference to a method) or to
contribute additional quick fixes and quick assists. To avoid
unnecessary plug-in activation these contributions need a mechanism to
describe their availability. This is in particular important when an
operation will load additional plug-ins. Consider the following
scenario: a JSP plug-in provides a rename method participant to adapt
JSP files during a method rename. Loading this participant is only
necessary if an accessible method (e.g not private, ...) is renamed and
if the user does JSP development at all. Since contributions are
declared in XML, the provider of such a rename participant needs some
sort of expression language to define the participant's availability. Up
to now each extension point provider invented its own expression
language resulting in the following disadvantages:</p>
<ul>
<li><i>different functionality</i>: for example the pop-up menu
contribution mechanism supports and, or, filter, systemProperty, ...,
expressions whereas the property page contribution mechanism only
supports filter expressions.</li>
<li><i>different syntax</i>: the Java object browser extension point
uses an attribute type to test for the element's type whereas the menu
contribution mechanism uses an attribute objectClass to do so.</li>
<li><i>code duplication</i></li>
</ul>
<p>To avoid this proliferation one homogeneous expression evaluation
mechanism is proposed.</p>
<h3><a name="property_evaluation">Existing Mechanisms</a></h3>
<p>Why implementing a new mechanism and not simply promote one of the
existing ones? The mechanism provided by Platform/UI to control the
enablement state of action contributions is very similar to the one
proposed in this document. For the implementation of the refactoring
participants a new mechanism got implemented due to the following
reasons:</p>
<ul>
<li><i>Limited extensibility</i>: the extensibility of the expression
language provided by Platform/UI is based on the adapter mechanism.
This mechanism only supports one adapter per type. This means as soon
as one plug-in adds new a filter to type ICompilationUnit, no other
plug-in can add any other filter to that type.</li>
<li><i>Handling of non-loaded plug-ins</i>: if the plug-in providing a
filter implementation has not been loaded yet, the filter expression
evaluates to true. This may result in unneeded plug-in activation. The
new implementation uses the three values: FALSE, TRUE, NOT_LOADED.</li>
<li><i>Adapters</i>: action enablement in the UI supports the notion of
"adaptable". If requested, the workbench adapts the element to be
checked to <code>IContributorResourceAdapter</code>. In an open
architecture the class to which the element is to be adapted must be
configurable. Consider a call hierarchy view whose model is a wrapper
around Java elements. To enable Java refactoring actions on call
hierarchy nodes, the refactoring should check if the element can be
adapted to IJavaElement.</li>
<li><i>Layering</i>: refactoring, search, etc. will become core
functionality in 3.0. Therefore expression evaluation must be provided
by a non UI plug-in.</li>
</ul>
<h3>Proposed Solution</h3>
<p>The proposed solution is divided into two sections: </p>
<ul>
<li><i>section one</i> introduces a common expression language similar to the
one provided by Platform/UI. This language avoids the shortcomings
described above.</li>
<li><i>section two</i> discusses the topic how to convert XML
elements (e.g instances of IConfigurationElements) into expression
objects.</li>
</ul>
<h4>Common XML expression language</h4>
<p>An examination of the existing "expression languages" defined by the
different Eclipse plug-ins shows a common set of used expression types.
This sections defines a common expression language considering the
existing languages and their functionality.</p>
<p><i><u>Boolean operators</u></i></p>
<p>The expression language provides standard expressions for the Boolean
operators and, or and not.</p>
<p><i><u>Instanceof expression</u></i></p>
<p>The most frequently used expression in current XML files is one to test if an object conforms to a certain type. Most extension points use a special attribute (e.g. objectClass) to represent an instance of check. The common XML expression language provides a special XML element to represent instance of checks. A typical usage looks as follows:</p>
<blockquote><pre>&lt;instanceof value=&quot;org.eclipse.jdt.core.IJavaElement&quot;/&gt;</pre></blockquote>
<p>The above expression tests, if the object under inspection (in most cases the element selected in the user interface) is of instance &quot;org.eclipse.jdt.core.IJavaElement&quot;.</p>
<p><a name="test_expression0"><i><u>Test expression</u></i></a></p>
<p>Besides instance of checks the new expression language defines an extensible &lt;test&gt; element to support property testing. The &lt;test&gt; element is comparable to the &lt;filter&gt; element used in Platform/UI. The test element is used as follows:</p>
<blockquote>
<pre>
&lt;and&gt;
&lt;instanceof value=&quot;org.eclipse.core.resources.IFile&quot;/&gt;
&lt;test property="matchesPattern" value="*.html"/&gt;
&lt;/and&gt;</pre>
</blockquote>
<p>The above expression evaluates to true if the object under inspection is of type &quot;org.eclipse.core.resources.IFile&quot; and its file name matches the pattern &quot;*.html&quot;. But who actually provides the code to do the name pattern test. Predefining a set of properties to test is too limiting. The set of tests must be open. In Java, properties are either represented by fields or by methods.
Therefore adding new properties to a type means adding new fields or
adding new methods. The test mechanism has to support adding new
properties to library classes. Therefore properties have to be
implemented by additional methods. The major characteristics of the
extensible test mechanism are:</p>
<ul>
<li>types are enriched with new methods using a type extender, meaning
that the code of the method is provided by a different class.</li>
<li>a type extender implements a set of methods.</li>
<li>type extenders and their methods are defined in XML as extension
points. This is required to check if an extender provides a method to
test a property without having to activate it.</li>
<li>testing for an unknown property (e.g calling an undefined method)
results in a core exception. This is a programming error.</li>
</ul>
<p>Below follows a type extender that adds new methods for property testing
to the type org.eclipse.core.resources.IFile:</p>
<blockquote><pre>&lt;extension point="org.eclipse.jdt.typeExtenders"&gt;
&lt;typeExtender
id="org.eclipse.jdt.ui.IResourceTypeExtender"
type="org.eclipse.core.resources.IResource"
methods="matchesPattern, projectNature, canDelete"
class="org.eclipse.jdt.internal.corext.refactoring.participants.
xml.ResourceTypeExtender"/&gt;
&lt;/extension&gt;</pre>
</blockquote>
<p>The attributes of the typeExtender element have the following
meaning:</p>
<ul>
<li>id: a unique id</li>
<li>type: the type which gets "enriched" with new methods</li>
<li>methods: the list of methods provided by the extender.</li>
<li>class: the implementing class</li>
</ul>
<p>The implementation of a type extender is comparable to the
implementation of an IActionFilter, except that more than one argument
can be passed and that the signature is Object and not String based. The
concrete implementation for the above type extender declaration looks
like this:</p>
<pre><font size="-1">public class ResourceExtender extends TypeExtender {
private static final String PROPERTY_MATCHES_PATTERN= "matchesPattern"; //$NON-NLS-1$
private static final String PROJECT_NATURE = "projectNature"; //$NON-NLS-1$
private static final String CAN_DELETE= "canDelete"; //$NON-NLS-1$
public Object invoke(Object receiver, String method, Object[] args) {
IResource resource= (IResource)receiver;
if (PROPERTY_MATCHES_PATTERN.equals(method)) {
String fileName= resource.getName();
StringMatcher matcher= new StringMatcher((String)args[0], false, false);
return Boolean.valueOf(matcher.match(fileName));
} else if (PROJECT_NATURE.equals(method)) {
try {
IProject proj = resource.getProject();
return Boolean.valueOf(proj.isAccessible() &amp;&amp; proj.hasNature((String)args[0]));
} catch (CoreException e) {
return Boolean.FALSE;
}
} else if (CAN_DELETE.equals(method)) {
return Boolean.valueOf(canDelete(resource));
}
Assert.isTrue(false);
return null;
}
private boolean canDelete(IResource resource) {
if (!resource.exists() || resource.isPhantom())
return false;
if (resource.getType() == IResource.ROOT || resource.getType() == IResource.PROJECT)
return false;
return true;
}
</font>}</pre>
<p>Below is a&nbsp;XML expression that uses one of the new methods to test
a property:</p>
<blockquote><pre>&lt;and&gt;
&lt;instanceof value="org.eclipse.core.resources.IResource"/&gt;
&lt;test property="canDelete"/&gt;
&lt;/and&gt;</pre>
</blockquote>
<p>Sometime a property test needs more than one arguments. If this is
the case the arguments have to be passed using additional child
elements. An example which validates a name using the method
IWorkspace.validateName looks as follows:</p>
<blockquote><pre>&lt;test property="validateName"&gt;
&lt;string&gt;/org.eclipse.demo/A.java&lt;/string&gt;
&lt;string&gt;FILE&lt;/string&gt;
&lt;/test&gt;</pre>
</blockquote>
<p>Instead of wrapping existing methods with a type extender there
could be a default type extender that uses reflection to call the
method. Doing so will support testing if a Java method (and object of type org.eclipse.jdt.core.IMethod) is a main method without providing a special type extender for Java methods.
IMethod already provides a method isMainMethod(). An additional advantage
is that more tests can be evaluated if plug-ins aren't loaded yet (the
class of the element to be tested is already loaded since an instance
exists). I did some performance measurements: dispatching one million
method calls via reflection takes 260ms on my PC, dispatching the same
number of methods via a type extender takes 110ms. The test case assumes
that there is only one type extender for the type and that the provided
method is the third one defined, meaning that two unnecessary string
compare operations take place. Given these numbers and the advantages of
such an support I propose to add it.</p>
<p><i><u>With expression</u></i></p>
<p>Test expressions don't allow to specify the object they inspect. They
work on a default object, which for most extension points is the object
selected in the user interface. However, the enablement logic of some
extension points need to test other objects as well. Examples for other
objects to test are system properties or plug-in state. The current expression language uses special XML element like &lt;systemProperty&gt; and &lt;pluginState&gt; for this purpose. The new implementation proposes a with
expression. It can be used to change the object to be inspected for all its
child expressions. For example, the following XML snippet tests two
system properties:</p>
<blockquote><pre>&lt;with variable="system"&gt;
&lt;and&gt;
&lt;test property="os.name" value="Windows XP"/&gt;
&lt;test property="os.version" value="5.1"/&gt;
&lt;/and&gt;
&lt;/with&gt;</pre>
</blockquote>
<p>The plug-in that evaluates the extension point is responsible for
providing the set of available variables. For example, the code that
evaluates refactoring participants provides the follow variables:</p>
<ul>
<li><i>selection:</i> its value is a collection containing the objects
to be refactored</li>
<li><i>affectedProjects</i>: its value is a collection containing the
projects affected by the refactoring</li>
<li><i>system</i>: its value is the System class</li>
<li><i>defaultVariable</i>: will be used if no with expression element
is active. Is an alias for the variable selection.</li>
</ul>
<p>If the variable doesn't exist, the with expression will throw a core exception. Analogous to the test expression a type extender can be used to
allow contributors to extends the set of available variables.</p>
<p>Most of the time child expression will be combined using the and
operator. To avoid deep nesting XML &quot;and&quot; will be the default
for combining children. It can therefore be omitted. The same applies to
the adapt, iterate, and enablement expression defined in the following sections.</p>
<p><u><i>Adapt expression</i></u></p>
<p>As noted in the motivation section it isn't sufficient to support
IResource adaption only. To solve this problem a special adapt
expression is provided. The following example adapts the object to be
inspected to the interface IFile:</p>
<blockquote><pre>&lt;adapt type="org.eclipse.core.resources.IFile"&gt;
&lt;test property="canDelete"/&gt;
&lt;/adapt&gt;</pre>
</blockquote>
<p>Like the with expression the adapt expression changes the object to
inspect for all its children. The new object is the one returned from
IAdapter.getAdapter(). If the adaption fails, the expression evaluates
to false.</p>
<p>Analogous to the test expression the adapt expression is supposed to
return NOT_LOADED, if either the class referenced by the type attribute
(e.g. org.eclipse.core.resources.IFile) or the code
implementing the adapter isn't loaded yet. Adapters are contributed in
code. As a result there isn't any way right now to determine which
plug-in provides which kind of adapters. This problem is also discussed
in two bugzilla reports (<a
href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=32436">32436</a>
and <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=32498">32498</a>).
To solve this problem, plug-in must enumerate the adapters they provide. This is best done via a corresponding extension point. In principal such an extension point looks like this:</p>
<blockquote><pre>&lt;extension point=org.eclipse.core.expressions.adapt"&gt;
&lt;adapter objectType="org.eclipse.jdt.core.ICompilationUnit"
adapterType="org.eclipse.core.resources.IFile"/&gt;
&lt;/extension&gt;</pre></blockquote>
<p>This extension point declares that there is an adapter that adapts an object of type ICompilationUnit to an object of type IFile. The needed information can be provided in two different ways:</p>
<ol>
<li>as suggested in the PRs (<a
href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=32436">32436</a>
and <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=32498">32498</a>) adapter factories get contributed via XML.To allow
the adapt expression to use this information IAdapterManager has to
provide API to retrieve an adapter by specifying the type as a string.
The proposed signature is Object getAdapter(Object element, String
adapterType). The method throws a CoreExcpetion if the plug-in
providing the adapter isn't loaded yet. Alternatively two separate
methods hasAdapter(Object element, String adapterType) and
resolveClass(Object element, String adapterType) can be implemented. Adapters directly implemented in the getAdapter method (not via an adapter factory) must be declared in XML as well. This can be done using the XML element as proposed in PR <a
href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=32436">32436</a> without providing the class attribute in the &lt;adapterFactory&gt; element.</li>
<li>we continue contributing adapters and adapter factories via code. For the &lt;adapt&gt;
expression we provide a special XML element similar to the one from above. This
information is sufficient to decide if the adapt expression has to
return NOT_LOADED.
</li>
</ol>
Preferred is solution (1). If core decides to not support adapter
configuration via XML then solution (2) has to be implemented.
<p><u><i>Dealing with collection of elements</i></u></p>
<p>Several expressions are evaluated on a collection of
objects (for example refactoring participants, menu contributions, ..).
Up to now the iteration over collections is implicitly coded into the
enclosing XML element, which isn't part of the expression itself. The
new mechanism provides explicit expression elements to deal with
collections of objects. The following element</p>
<blockquote><pre>&lt;count value="*"/&gt;</pre></blockquote>
<p>is used to check the number of objects in a collection and the syntax
of the attribute value is equal to the enablesFor attribute used for
object contributions. To iterate over a collection an element</p>
<blockquote><pre>&lt;iterate operator="..."&gt;</pre></blockquote>
<p>is provided. The operator attribute can either be "and" or "or". It
determines how the evaluation results of all objects in the list are
combined. The default operator is "and". Using these expression the
enablement of a typical contribution can be described as follows:</p>
<blockquote><pre>&lt;with variable="selection"&gt;
&lt;count value="+"/&gt;
&lt;iterate operator="and"/&gt;
&lt;adapt type="org.eclipse.core.resources.IFile"&gt;
&lt;test property="matchesName" value="*.gif"/&gt;
&lt;test property="canDelete"/&gt;
&lt;/adapt&gt;
&lt;/iterate&gt;
&lt;/with&gt;</pre>
</blockquote>
<p>The expression only evaluates to true if the selection contains one
or more objects and all objects fulfill the expression defined by the
adapt element.</p>
<p><u><i>Enablement expression</i></u></p>
<p>XML expressions are mostly used to define the availability of an
extension point contribution. To separate the expression from other
child elements the common expression language provides an enablement
element. Its use is as follows:</p>
<blockquote><pre>&lt;renameParticipant
id="launchConfigUpdater"
class="org.eclipse...LaunchConfigUpdater"&gt;
&lt;enablement&gt;
...
&lt;/enablement&gt;
&lt;/renameParticipant&gt;</pre>
</blockquote>
<p><u><i>Extension Point Schema</i></u></p>
<p>PDE supports to check the syntax of an extension point contribution.
To do so the extension point provider has to provide a XML schema file
that defines the point's structure. PDE currently supports a subset of the schema language provided by W3C. Due to the fact, that the set of expressions is open (in contrast to the expression language provided by Platform/UI) the following schema concepts have to be supported by PDE to enble schema validation for the common expression language:</p>
<ul>
<li><a href="http://www.w3.org/TR/xmlschema-0/#SubsGroups">substitution
groups</a>: to be able to define composite expression like and, or,
with, in a polymorphic way.</li>
<li><a href="http://www.w3.org/TR/xmlschema-0/#abstract">abstract
elements and extension</a>: to define the inheritance relationship
between the XML expression elements.This is a prerequisite to be able
to use substitution groups.</li>
</ul><h4><a name="converting">Converting XML elements into expressions</a></h4>
<p>Having a set of common expressions is not enough. We also need a
mechanism to convert XML elements into expressions. The platform already
converts XML elements into configuration elements while reading plug-in
manifest files. Therefore a component responsible for creating
expression has to take configuration elements has its input. Open is the
question if the component only needs to supports the common expressions
defined above. Or does it have to be extensible to support other
expressions as well? The proposed solution allows extending the set of
expression. The component consists of the following types:</p>
<ul>
<li><i>TestResult</i>: this class encapsulates the three result values
FALSE, TRUE and NOT_LOADED and defines the logic for the standard
operators and, or and not.</li>
<li><i>Expression</i>: the abstract base class of all expressions,
defining a method <code>evaluate</code> which returns a TestResult.</li>
<li><i>IElementHandler</i>: an interface defining a method to convert a
configuration element into a corresponding expression.The method
returns null if it can't handle the configuration element.</li>
<li><i>ExpressionCreator</i>: concrete class that is instantiated with
a list of element handlers and controls the actual conversion.</li>
</ul>
<p>An element handler responsible to convert configuration elements into
common expressions looks like this:</p>
<blockquote><pre><font size="-1">public class CommonElementHandler implements IElementHandler {
public Expression convert(IConfigurationElement element, ExpressionCreator creator) throws CoreException {
String name= element.getName();
if (TestExpression.NAME.equals(name)) {
return new TestExpression(element);
}
if (AndExpression.NAME.equals(name)) {
AndExpression result= new AndExpression();
creator.processChildren(result, element);
return result;
}
...
return null;
}
}</font></pre></blockquote>
<p>Expression creators are used by the plug-ins reading an extension
point to actually convert the configuration element into an expression.
A typical code snippet looks like this:</p>
<blockquote><pre><font size="-1">ExpressionCreator creator= new ExpressionCreator(new IElementHandler[] {new StandardElementHandler() });
Expression exp= creator.parse(configurationElement);
exp.evaluate(...);</font></pre>
</blockquote>
<p>When creating an expression creator the reader of an extension point
passes the set of element handlers to be used. Therefore the reader
limits the set of valid expressions that can be used by a contribution.</p>
<p>The approach of converting existing configuration elements into
expressions has the disadvantage that both the configuration element
tree and the expression tree have to be managed and that both consume
memory. Possible soultions are:</p>
<ol>
<li type="1">we accept this situation.</li>
<li type="1">the plug-in registry converts XML expression elements into
a corresponding expression tree while reading the plug-in XML file.
This reduces memory consumption and provides a convenient API for
clients (they can directly call a method getExpression()). But to avoid
unnecessary plug-in loading during registry creation the set of
expressions has to be fixed for all extension points.</li>
<li type="1">the configuration element itself (or a special subclass)
offers a method to convert the element and its children into a
corresponding expression. The proposed API is
IConfigurationElement.convertToExpression(ExpressionCreator creator) to
convert the configuration element and a method
IConfigurationElement.getExpression() to access the expression. If the
element hasn't been converted yet, getExpression() returns null. The
problem with this approach is that, after conversion, the configuration
tree sstructure doesn't fully reflect the content of the plugin.xml
file anymore. Since extension points are normally interpreted by the
plug-in that defines the extension point this doesn't cause any harm.
Additionally the conversion doesn't take place automatically so it is
fully backwards compatible.</li>
</ol>
<p>I suggest option 3.</p>
<h3>Using common expressions inside Platform/UI</h3>
<p>The enablement logic used for action contribution looks similar to
the proposed solution. To avoid code duplication the existing internal
implementation should be deprecated and the common XML expression
language should be used instead. To ensure backward compatibility the
current action filters can be made accessible through type extenders in
the following way:</p>
<blockquote>
<blockquote><pre>&lt;extension point="org.eclipse.jdt.typeExtenders"&gt;
id="org.eclipse.jdt.ui.IResourceTypeExtender"
type="org.eclipse.core.resources.IResource"
methods="name, extension, path, readOnly, projectNature, ..."
class="org.eclipse.core.resources.ResourceExtender"/&gt;
&lt;/extension&gt;</pre>
</blockquote>
</blockquote>
<p>The implementation of the resource extender can either be an adapter
to the existing resource action filter or a new implementation is
provided. An adapter implementation looks like this:</p>
<pre>public class ResourceExtender extends TypeExtender {
private WorkbenchResource fActionFilter;
public Object invoke(Object receiver, String method, Object[] args) {
return Boolean.valueOf(fActionFilter.testAttribute(receiver, method, (String)args[0]));
}
}</pre>
<p>Additionally, corresponding XML elements have to be provided for
&lt;objectClass&gt;, &lt;objectState&gt;, &lt;pluginState&gt; and
&lt;systemProperty&gt;. The elements &lt;objectState&gt; and
&lt;objectClass&gt; can be mapped to the &lt;test&gt; element. The
elements &lt;pluginState&gt; and &lt;systemProperty&gt; can be
implemented similar to the &lt;with&gt; and &lt;test&gt; elements. So
Platform/UI only needs to provide corresponding expressions and a
special element handler to convert the configuration elements into those
expressions when reading the extension point contribution.</p>
<h3>The new Home</h3>
<p>The last remaining question is, where to put this new mechanism.
Given the requirement that core and UI plug-ins want to use XML
expressions I see two possible solutions:</p>
<ol>
<li>both the conversion component and the common XML expression
language is hosted by org.eclipse.core.runtime. A proposal for the new
package name is org.eclipse.core.expressions</li>
<li type="1">only the conversion mechanism is hosted by
org.eclipse.core.runtime. This is necessary to avoid doubling the
structure in memory as outlined in section <a href="#converting">Converting
XML elements into expressions</a>. The common XML expression language
is located in a separate plug-in org.eclipse.core.expressions.</li>
</ol>
<p>I suggest solution 2.</p>
</body>
</html>