Skip to content

Commit

Permalink
Implement alternative JavaBean support
Browse files Browse the repository at this point in the history
Add support for multiple JavaBean specification implementations.

If the class java.beans.BeanInfo is available, the full JavaBeans
implementation is used (same behaviour as before this patch).

If the full JavaBeans implementation is not available, a built-in,
stand-alone implementation is used that only provides the JavaBeans
functionality that does not depend on any of the java.beans.* classes
(essentially getter/setter support).

Note that the configuration system properties are intended for testing
only. They are NOT part of the public API.
  • Loading branch information
markt-asf committed May 22, 2023
1 parent ff7a8d6 commit 015803d
Show file tree
Hide file tree
Showing 8 changed files with 719 additions and 60 deletions.
6 changes: 6 additions & 0 deletions api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@
<version>5.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>

<properties>
Expand Down
74 changes: 19 additions & 55 deletions api/src/main/java/jakarta/el/BeanELResolver.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2022 Oracle and/or its affiliates and others.
* Copyright (c) 1997, 2023 Oracle and/or its affiliates and others.
* All rights reserved.
* Copyright 2004 The Apache Software Foundation
*
Expand All @@ -20,10 +20,6 @@

import static jakarta.el.ELUtil.getExceptionMessageString;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.reflect.InvocationTargetException;
Expand Down Expand Up @@ -146,20 +142,20 @@ public BeanProperties get(Object key) {
/*
* Defines a property for a bean.
*/
final static class BeanProperty {
abstract static class BeanProperty {

final private Class<?> baseClass;
final private PropertyDescriptor descriptor;
final private Class<?> type;
private Method readMethod;
private Method writeMethod;

public BeanProperty(Class<?> baseClass, PropertyDescriptor descriptor) {
public BeanProperty(Class<?> baseClass, Class<?> type) {
this.baseClass = baseClass;
this.descriptor = descriptor;
this.type = type;
}

public Class<?> getPropertyType() {
return descriptor.getPropertyType();
return this.type;
}

public boolean isReadOnly(Object base) {
Expand All @@ -168,67 +164,35 @@ public boolean isReadOnly(Object base) {

public Method getReadMethod(Object base) {
if (readMethod == null) {
readMethod = ELUtil.getMethod(baseClass, base, descriptor.getReadMethod());
readMethod = ELUtil.getMethod(baseClass, base, getReadMethod());
}
return readMethod;
}

public Method getWriteMethod(Object base) {
if (writeMethod == null) {
writeMethod = ELUtil.getMethod(baseClass, base, descriptor.getWriteMethod());
writeMethod = ELUtil.getMethod(baseClass, base, getWriteMethod());
}
return writeMethod;
}

abstract Method getWriteMethod();

abstract Method getReadMethod();
}

/*
* Defines the properties for a bean.
*/
final static class BeanProperties {

private final Map<String, BeanProperty> propertyMap = new HashMap<>();

public BeanProperties(Class<?> baseClass) {
PropertyDescriptor[] descriptors;
try {
BeanInfo info = Introspector.getBeanInfo(baseClass);
descriptors = info.getPropertyDescriptors();
for (PropertyDescriptor descriptor : descriptors) {
propertyMap.put(descriptor.getName(), new BeanProperty(baseClass, descriptor));
}
/**
* Populating from any interfaces solves two distinct problems:
* 1. When running under a security manager, classes may be
* unaccessible but have accessible interfaces.
* 2. It enables default methods to be included.
*/
populateFromInterfaces(baseClass, baseClass);
} catch (IntrospectionException ie) {
throw new ELException(ie);
}
abstract static class BeanProperties {

}
protected final Map<String, BeanProperty> propertyMap = new HashMap<>();
protected final Class<?> baseClass;

private void populateFromInterfaces(Class<?> baseClass, Class<?> aClass) throws IntrospectionException {
Class<?> interfaces[] = aClass.getInterfaces();
if (interfaces.length > 0) {
for (Class<?> ifs : interfaces) {
BeanInfo info = Introspector.getBeanInfo(ifs);
PropertyDescriptor[] pds = info.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
if (!this.propertyMap.containsKey(pd.getName())) {
this.propertyMap.put(pd.getName(), new BeanProperty(
baseClass, pd));
}
}
}
}
Class<?> superclass = aClass.getSuperclass();
if (superclass != null) {
populateFromInterfaces(baseClass, superclass);
}
BeanProperties(Class<?> baseClass) {
this.baseClass = baseClass;
}

public BeanProperty getBeanProperty(String property) {
return propertyMap.get(property);
}
Expand Down Expand Up @@ -568,7 +532,7 @@ private BeanProperty getBeanProperty(ELContext context, Object base, Object prop

BeanProperties beanProperties = properties.get(baseClass);
if (beanProperties == null) {
beanProperties = new BeanProperties(baseClass);
beanProperties = BeanSupport.getInstance().getBeanProperties(baseClass);
properties.put(baseClass, beanProperties);
}

Expand Down
68 changes: 68 additions & 0 deletions api/src/main/java/jakarta/el/BeanSupport.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (c) 2023 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.el;

import jakarta.el.BeanELResolver.BeanProperties;

/*
* Provides an abstraction so the BeanELResolver can obtain JavaBeans specification support via different
* implementations.
*/
abstract class BeanSupport {

private static final BeanSupport beanSupport;

static {
// Only intended for unit tests. Not intended to be part of public API.
boolean doNotCacheInstance = Boolean.getBoolean("jakarta.el.BeanSupport.doNotCacheInstance");
if (doNotCacheInstance) {
beanSupport = null;
} else {
beanSupport = createInstance();
}
}

private static BeanSupport createInstance() {
// Only intended for unit tests. Not intended to be part of public API.
boolean useFull = !Boolean.getBoolean("jakarta.el.BeanSupport.useStandalone");

if (useFull) {
// If not explicitly configured to use standalone, use the full implementation unless it is not available.
try {
Class.forName("java.beans.BeanInfo");
} catch (Exception e) {
// Ignore: Expected if using modules and java.desktop module is not present
useFull = false;
}
}
if (useFull) {
// The full implementation provided by the java.beans package
return new BeanSupportFull();
} else {
// The cut-down local implementation that does not depend on the java.beans package
return new BeanSupportStandalone();
}
}

static BeanSupport getInstance() {
if (beanSupport == null) {
return createInstance();
}
return beanSupport;
}

abstract BeanProperties getBeanProperties(Class<?> type);
}
93 changes: 93 additions & 0 deletions api/src/main/java/jakarta/el/BeanSupportFull.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright (c) 2023 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.el;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;

import jakarta.el.BeanELResolver.BeanProperties;
import jakarta.el.BeanELResolver.BeanProperty;

class BeanSupportFull extends BeanSupport {

@Override
BeanProperties getBeanProperties(Class<?> type) {
return new BeanPropertiesFull(type);
}

static final class BeanPropertiesFull extends BeanProperties {

BeanPropertiesFull(Class<?> baseClass) throws ELException {
super(baseClass);
try {
BeanInfo info = Introspector.getBeanInfo(this.baseClass);
PropertyDescriptor[] pds = info.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
this.propertyMap.put(pd.getName(), new BeanPropertyFull(baseClass, pd));
}
/*
* Populating from any interfaces causes default methods to be included.
*/
populateFromInterfaces(baseClass);
} catch (IntrospectionException ie) {
throw new ELException(ie);
}
}

private void populateFromInterfaces(Class<?> aClass) throws IntrospectionException {
Class<?> interfaces[] = aClass.getInterfaces();
if (interfaces.length > 0) {
for (Class<?> ifs : interfaces) {
BeanInfo info = Introspector.getBeanInfo(ifs);
PropertyDescriptor[] pds = info.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
if (!this.propertyMap.containsKey(pd.getName())) {
this.propertyMap.put(pd.getName(), new BeanPropertyFull(this.baseClass, pd));
}
}
populateFromInterfaces(ifs);
}
}
Class<?> superclass = aClass.getSuperclass();
if (superclass != null) {
populateFromInterfaces(superclass);
}
}
}

static final class BeanPropertyFull extends BeanProperty {

private final PropertyDescriptor descriptor;

BeanPropertyFull(Class<?> owner, PropertyDescriptor descriptor) {
super(owner, descriptor.getPropertyType());
this.descriptor = descriptor;
}

@Override
Method getWriteMethod() {
return descriptor.getWriteMethod();
}

@Override
Method getReadMethod() {
return descriptor.getReadMethod();
}
}
}
Loading

0 comments on commit 015803d

Please sign in to comment.