e.g.
class tester
{
#Test
public void testBeanUtils() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException
{
Stranger stranger = new Stranger();
BeanUtils.setProperty(stranger,"name","wener");
BeanUtils.setProperty(stranger,"xname","xwener");
BeanUtils.setProperty(stranger,"yname","ywener");
System.out.println(stranger);
}
#Data// lombok annotation generate all setter and getter
public static class Stranger
{
#Accessors(chain = true)// generate chained setter
String name;
String xname;
String yname;
public Stranger setYname(String yname)// no lombok, still not work
{
this.yname = yname;
return this;
}
}
}
My output:
TestValues.Stranger(name=null, xname=xwener, yname=null)
What's wrong with this? chain setter is a good thing.
Any suggests ?
EDIT
Back to this problem again.This time I can not remove the Accessors chain.
Now, I use the commons-lang3 to achieve.
// force access = true is required
Field field = FieldUtils.getField(bean.getClass(), attrName, true);
field.set(bean,value);
For those who got the same problem.
You can use the FluentPropertyBeanIntrospector implementation:
"An implementation of the BeanIntrospector interface which can detect write methods for properties used in fluent API scenario."
https://commons.apache.org/proper/commons-beanutils/apidocs/org/apache/commons/beanutils/FluentPropertyBeanIntrospector.html
PropertyUtils.addBeanIntrospector(new FluentPropertyBeanIntrospector());
BeanUtils.setProperty( this.o, "property", "value" );
That's simple: BeanUtils are rather strange and so is Introspector it uses:
Although BeanUtils.setProperty declares some exceptions, it seems to silently ignore the non-existence of the property to be set. The ultimate culprit is the Introspector which simply requires the voidness of setter.
I'd call it broken by design, but YMMV. It's an old class and fluent interfaces weren't invented yet in those dark times. Use Accessors(chain=false) to disable chaining.
More important: Use the source. Get it and get a debugger (it's already in your IDE) to find it out yourself (still feel free to ask if it doesn't work, just try a bit harder).
In my project we use chained accessors across the board, so setting chain=false was not an option. I ended up writing my own introspector, which is similar to the one recommended by #mthielcke, and may be registered in the same way.
Introspector
import org.apache.commons.beanutils.BeanIntrospector;
import org.apache.commons.beanutils.IntrospectionContext;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Stream;
import lombok.extern.slf4j.Slf4j;
/**
* Allows {#link org.apache.commons.beanutils.BeanUtils#copyProperties(Object, Object)} to copy properties across beans whose
* properties have been made <b>fluent</b> through Lombok
* {#link lombok.experimental.Accessors}, {#link lombok.Setter} and {#link lombok.Getter} annotations.
*
* #author izilotti
*/
#Slf4j
public class LombokPropertyBeanIntrospector implements BeanIntrospector {
/**
* Performs introspection. This method scans the current class's methods for property write and read methods which have been
* created by the Lombok annotations.
*
* #param context The introspection context.
*/
#Override
public void introspect(final IntrospectionContext context) {
getLombokMethods(context).forEach((propertyName, methods) -> {
if (methods[0] != null && methods[1] != null) {
final PropertyDescriptor pd = context.getPropertyDescriptor(propertyName);
try {
if (pd == null) {
PropertyDescriptor descriptor = new PropertyDescriptor(propertyName, methods[1], methods[0]);
context.addPropertyDescriptor(descriptor);
}
} catch (final IntrospectionException e) {
log.error("Error creating PropertyDescriptor for {}. Ignoring this property.", propertyName, e);
}
}
});
}
private Map<String, Method[]> getLombokMethods(IntrospectionContext context) {
Map<String, Method[]> lombokPropertyMethods = new HashMap<>(); // property name, write, read
Stream.of(context.getTargetClass().getMethods())
.filter(this::isNotJavaBeanMethod)
.forEach(method -> {
if (method.getReturnType().isAssignableFrom(context.getTargetClass()) && method.getParameterCount() == 1) {
log.debug("Found mutator {} with parameter {}", method.getName(), method.getParameters()[0].getName());
final String propertyName = propertyName(method);
addWriteMethod(lombokPropertyMethods, propertyName, method);
} else if (!method.getReturnType().equals(Void.TYPE) && method.getParameterCount() == 0) {
log.debug("Found accessor {} with no parameter", method.getName());
final String propertyName = propertyName(method);
addReadMethod(lombokPropertyMethods, propertyName, method);
}
});
return lombokPropertyMethods;
}
private void addReadMethod(Map<String, Method[]> lombokPropertyMethods, String propertyName, Method readMethod) {
if (!lombokPropertyMethods.containsKey(propertyName)) {
Method[] writeAndRead = new Method[2];
lombokPropertyMethods.put(propertyName, writeAndRead);
}
lombokPropertyMethods.get(propertyName)[1] = readMethod;
}
private void addWriteMethod(Map<String, Method[]> lombokPropertyMethods, String propertyName, Method writeMethod) {
if (!lombokPropertyMethods.containsKey(propertyName)) {
Method[] writeAndRead = new Method[2];
lombokPropertyMethods.put(propertyName, writeAndRead);
}
lombokPropertyMethods.get(propertyName)[0] = writeMethod;
}
private String propertyName(final Method method) {
final String methodName = method.getName();
return (methodName.length() > 1) ? Introspector.decapitalize(methodName) : methodName.toLowerCase(Locale.ENGLISH);
}
private boolean isNotJavaBeanMethod(Method method) {
return !isGetter(method) || isSetter(method);
}
private boolean isGetter(Method method) {
if (Modifier.isPublic(method.getModifiers()) && method.getParameterTypes().length == 0) {
if (method.getName().matches("^get[A-Z].*") && !method.getReturnType().equals(Void.TYPE)) {
return true;
}
return method.getName().matches("^is[A-Z].*") && method.getReturnType().equals(Boolean.TYPE);
}
return false;
}
private boolean isSetter(Method method) {
return Modifier.isPublic(method.getModifiers())
&& method.getReturnType().equals(Void.TYPE)
&& method.getParameterTypes().length == 1
&& method.getName().matches("^set[A-Z].*");
}
}
Registration
PropertyUtils.addBeanIntrospector(new LombokPropertyBeanIntrospector());
BeanUtils.copyProperties(dest, origin);
Related
The following Guice module binds a property file to the #Named annotation.
import com.google.inject.AbstractModule;
import com.google.inject.name.Names;
// Omitted: other imports
public class ExampleModule extends AbstractModule {
#Override
protected void configure() {
Names.bindProperties(binder(), getProperties());
}
private Properties getProperties() {
// Omitted: return the application.properties file
}
}
I can now inject properties directly into my classes.
public class Example {
#Inject
#Named("com.example.title")
private String title;
#Inject
#Named("com.example.panel-height")
private int panelHeight;
}
The values read from a properties file are strings but, as you can see in the example above, Guice is capable of doing type conversion for int fields.
Now, given the property com.example.background-color=0x333333 I would like to able to get the same type conversion for an arbitrary class, like:
public class Example {
#Inject
#Named("com.example.background-color")
private Color color;
}
Let's say that the Color class contains a static method decode() and I can obtain a new Color instance by calling Color.decode("0x333333").
How can I configure Guice to do this automatically and behind the scenes for me?
I found a solution by myself looking into the Guice sources, although I have to say it's not the prettiest (more on this later on).
First of all, we need to create a TypeConverter.
import com.google.inject.TypeLiteral;
import com.google.inject.spi.TypeConverter;
// Omitted: other imports
public class ColorTypeConverter implements TypeConverter {
#Override
public Object convert(String value, TypeLiteral<?> toType) {
if (!toType.getRawType().isAssignableFrom(Color.class)) {
throw new IllegalArgumentException("Cannot convert type " + toType.getType().getTypeName());
}
if (value == null || value.isBlank()) {
return null;
}
return Color.decode(value);
}
}
Then, a Matcher. I generalized.
import com.google.inject.TypeLiteral;
import com.google.inject.matcher.AbstractMatcher;
// Omitted: other imports
public class SubclassMatcher extends AbstractMatcher<TypeLiteral<?>> {
private final Class<?> type;
public SubclassMatcher(Class<?> type) {
this.type = type;
}
#Override
public boolean matches(TypeLiteral<?> toType) {
return toType.getRawType().isAssignableFrom(type);
}
}
Finally, add the following line to the Guice module.
import com.google.inject.AbstractModule;
// Omitted: other imports
public class ExampleModule extends AbstractModule {
#Override
protected void configure() {
binder().convertToTypes(new SubclassMatcher(Color.class), new ColorTypeConverter());
// Omitted: other configurations
}
}
Now, the following injection works.
public class Example {
#Inject
#Named("com.example.background-color")
private Color backgroundColor;
}
It could be prettier. There exists a com.google.inject.matcher.Matchers API which I wasn't able use and could have solved my problem without constructing my personal SubclassMatcher class. See, Matchers.subclassesOf(Class<?>). It's for sure my fault as I don't believe Google wouldn't think of this pretty common use-case. If you find a way to make it work, please leave a comment.
Guice can't do that for you.
I suppose the conversion from String to int happens upon injection and not when you call Names.bindProperties(...)
See the bindProperties methods:
/** Creates a constant binding to {#code #Named(key)} for each entry in {#code properties}. */
public static void bindProperties(Binder binder, Map<String, String> properties) {
binder = binder.skipSources(Names.class);
for (Map.Entry<String, String> entry : properties.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
binder.bind(Key.get(String.class, new NamedImpl(key))).toInstance(value);
}
}
/**
* Creates a constant binding to {#code #Named(key)} for each property. This method binds all
* properties including those inherited from {#link Properties#defaults defaults}.
*/
public static void bindProperties(Binder binder, Properties properties) {
binder = binder.skipSources(Names.class);
// use enumeration to include the default properties
for (Enumeration<?> e = properties.propertyNames(); e.hasMoreElements(); ) {
String propertyName = (String) e.nextElement();
String value = properties.getProperty(propertyName);
binder.bind(Key.get(String.class, new NamedImpl(propertyName))).toInstance(value);
}
}
They are just binding strings.
You could just copy one of them and create your own binding. If the property value is in a color format, bind it additionally as Color.
As an example:
public class GuiceColors {
public static class GameModule extends AbstractModule {
#Override
protected void configure() {
Properties props = new Properties();
try {
props.load(getClass().getResourceAsStream("application.properties"));
} catch (IOException e) {
e.printStackTrace();
}
bindPropertiesWithColors(props);
}
private void bindPropertiesWithColors(Properties properties) {
Binder binder2 = binder().skipSources(Names.class);
// use enumeration to include the default properties
for (Enumeration<?> e = properties.propertyNames(); e.hasMoreElements();) {
String propertyName = (String) e.nextElement();
String value = properties.getProperty(propertyName);
try {
Color decodedColor = Color.decode(value);
binder2.bind(Key.get(Color.class, Names.named(propertyName)))
.toInstance(decodedColor);
} catch (NumberFormatException ex) {
// property value cannot be decoded as color, ignore the exception
}
binder2.bind(Key.get(String.class, Names.named(propertyName))).toInstance(value);
}
}
}
public static class Example {
#Inject
#Named("com.example.background-color")
private Color color;
#Inject
#Named("com.example.background-color")
private String colorString;
}
public static void main(String[] args) {
Injector injector = Guice.createInjector(new GameModule());
System.out.println(injector.getInstance(Example.class).color);
System.out.println(injector.getInstance(Example.class).colorString);
}
}
with application.properties being:
com.example.background-color = 0x333333
This question seems awkward but we are facing a strange behaviour while retrieving the PropertyDescriptors of a javabean.
Here are the execution results on 1.6, 1.7 and 1.8 of a simple piece of code, compiled with 1.6 compliance.
Java 1.6 execution:
java.beans.PropertyDescriptor#4ddc1428 <- Not important
java.beans.IndexedPropertyDescriptor#7174807e <- Yes I have an indexed property
Java 1.7 execution:
java.beans.PropertyDescriptor[name=class; propertyType=class java.lang.Class; readMethod=public final native java.lang.Class java.lang.Object.getClass()] <- Not important
java.beans.IndexedPropertyDescriptor[name=values; indexedPropertyType=class java.lang.String; indexedReadMethod=public java.lang.String JavaBean.getValues(int)] <- Yes I have an indexed property
Java 1.8 execution:
java.beans.PropertyDescriptor[name=class; propertyType=class java.lang.Class; readMethod=public final native java.lang.Class java.lang.Object.getClass()] <- Not important
java.beans.PropertyDescriptor[name=values; propertyType=interface java.util.List; readMethod=public java.util.List JavaBean.getValues()] <- Ouch! This is not an indexed property anymore!
Why has it changed?
The javabean specs states about accessing a property with an index. It is not said as mandatory to use an array as the container of the indexed property. Am I wrong?
I read the specs and chapter 8.3.3 talks about Design Patterns for Indexed properties, not the strict rule.
How to make the previous behaviour coming back again without refactoring all the app ? < Old application, lot of code to modify, etc...
Thanks for the answers,
JavaBean class
import java.util.ArrayList;
import java.util.List;
public class JavaBean {
private List<String> values = new ArrayList<String>();
public String getValues(int index) {
return this.values.get(index);
}
public List<String> getValues() {
return this.values;
}
}
Main class
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
public class Test {
public static void main(String[] args) throws IntrospectionException {
PropertyDescriptor[] descs =
Introspector.getBeanInfo(JavaBean.class).getPropertyDescriptors();
for (PropertyDescriptor pd : descs) {
System.out.println(pd);
}
}
}
From JavaBeans 1.01 specification, section 7.2 “Indexed properties”:
A component may also expose an indexed property as a single array value.
Section 8.3 is describing the design patterns which introspection recognizes, in the absence of explicit BeanInfo. Section 8.3.3 is saying that only array properties will trigger automatic recognition of indexed properties.
You're technically correct; it is not mandatory to use an array. But if you don't, the spec says you have to provide your own BeanInfo to expose the property as an indexed property.
So the answer to your question's title is: Yes, Java 1.8 is JavaBean specs compliant.
I'm not sure why List properties were ever supported. Maybe a future JavaBeans specification was going to support them which has since been withdrawn.
As to your final question: I think you'll have to create a BeanInfo class for each class with List properties. I expect you can create a general superclass to make it easier, something like:
public abstract class ListRecognizingBeanInfo
extends SimpleBeanInfo {
private final BeanDescriptor beanDesc;
private final PropertyDescriptor[] propDesc;
protected ListRecognizingBeanInfo(Class<?> beanClass)
throws IntrospectionException {
beanDesc = new BeanDescriptor(beanClass);
List<PropertyDescriptor> desc = new ArrayList<>();
for (Method method : beanClass.getMethods()) {
int modifiers = method.getModifiers();
Class<?> type = method.getReturnType();
if (Modifier.isPublic(modifiers) &&
!Modifier.isStatic(modifiers) &&
!type.equals(Void.TYPE) &&
method.getParameterCount() == 0) {
String name = method.getName();
String remainder;
if (name.startsWith("get")) {
remainder = name.substring(3);
} else if (name.startsWith("is") &&
type.equals(Boolean.TYPE)) {
remainder = name.substring(2);
} else {
continue;
}
if (remainder.isEmpty()) {
continue;
}
String propName = Introspector.decapitalize(remainder);
Method writeMethod = null;
Method possibleWriteMethod =
findMethod(beanClass, "set" + remainder, type);
if (possibleWriteMethod != null &&
possibleWriteMethod.getReturnType().equals(Void.TYPE)) {
writeMethod = possibleWriteMethod;
}
Class<?> componentType = null;
if (type.isArray()) {
componentType = type.getComponentType();
} else {
Type genType = method.getGenericReturnType();
if (genType instanceof ParameterizedType) {
ParameterizedType p = (ParameterizedType) genType;
if (p.getRawType().equals(List.class)) {
Type[] argTypes = p.getActualTypeArguments();
if (argTypes[0] instanceof Class) {
componentType = (Class<?>) argTypes[0];
}
}
}
}
Method indexedReadMethod = null;
Method indexedWriteMethod = null;
if (componentType != null) {
Method possibleReadMethod =
findMethod(beanClass, name, Integer.TYPE);
Class<?> idxType = possibleReadMethod.getReturnType();
if (idxType.equals(componentType)) {
indexedReadMethod = possibleReadMethod;
}
if (writeMethod != null) {
possibleWriteMethod =
findMethod(beanClass, writeMethod.getName(),
Integer.TYPE, componentType);
if (possibleWriteMethod != null &&
possibleWriteMethod.getReturnType().equals(
Void.TYPE)) {
indexedWriteMethod = possibleWriteMethod;
}
}
}
if (indexedReadMethod != null) {
desc.add(new IndexedPropertyDescriptor(propName,
method, writeMethod,
indexedReadMethod, indexedWriteMethod));
} else {
desc.add(new PropertyDescriptor(propName,
method, writeMethod));
}
}
}
propDesc = desc.toArray(new PropertyDescriptor[0]);
}
private static Method findMethod(Class<?> cls,
String name,
Class<?>... paramTypes) {
try {
Method method = cls.getMethod(name, paramTypes);
int modifiers = method.getModifiers();
if (Modifier.isPublic(modifiers) &&
!Modifier.isStatic(modifiers)) {
return method;
}
} catch (NoSuchMethodException e) {
}
return null;
}
#Override
public BeanDescriptor getBeanDescriptor() {
return beanDesc;
}
#Override
public PropertyDescriptor[] getPropertyDescriptors() {
return propDesc;
}
}
I am facing the same issue. I am trying to save StartDate and End date as List from JSP but it is not saved and values are wiped out. In my project, there are start date and end date fields. I debugged BeanUtilsBean then I observed that fields do not have writeMethod. I have added one more setter method for each field in my class and it works.
java.beans.PropertyDescriptor[name=startDateStrings; propertyType=interface java.util.List; readMethod=public java.util.List com.webapp.tradingpartners.TradingPartnerNewForm.getStartDateStrings()]
This is a JavaBeans crapspec problem, which only allows void setters. If you want this-returning setters, then you can't have JavaBeans compatibility and there's nothing Lombok could do about it.
In theory, you could generate two setters, but then you'd have to call them differently and having two setters per field is simply too bad.
<cc:dateInput property='<%= "startDateStrings[" + row + "]" %>' onchange="setPropertyChangedFlag()"/>
<cc:dateInput property='<%= "endDateStrings[" + row + "]" %>' onchange="setPropertyChangedFlag()"/>
public List<String> getStartDateStrings() {
return startDateStrings;
}
public String getStartDateStrings(int index) {
return startDateStrings.get(index);
}
public void setStartDateStrings(int index, String value) {
startDateStrings.set(index, value);
}
public List<String> getEndDateStrings() {
return endDateStrings;
}
public String getEndDateStrings(int index) {
return endDateStrings.get(index);
}
public void setEndDateStrings(int index, String value) {
endDateStrings.set(index, value);
In a previous post Creating a ToolTip Managed bean
I was able to create a manged bean to collect and display tooltip text with only a single lookup and store them in an Application Scope variable. This has worked great.
I am on the rather steep part of the JAVA learning curve so please forgive me.
I have another managed bean requirement to create a HashMap Application Scope but this time it needs to be of a type String, Object. The application is where I have a single 'master' database that contains most of the code, custom controls, and XPages. This Master Database will point to One or More application databases that will store the Notes Documents specific to the application in question. So I have created in the Master a series of Application Documents that specify the RepIDs of the Application, Help and Rules databases specific to the Application along with a number of other pieces of information about the Application. This should allow me to reuse the same custom control that will open the specific DB by passing it the Application Name. As an example the Master Design DB might point to "Purchasing", "Customer Complaints", "Travel Requests" etc.
The code below works to load and store the HashMap, but I am having trouble retrieving the the data.
The compiler shows two errors one at the public Object get(String key) method and the other at mapValue = this.internalMap.get(key); in the getAppRepID method I think that it is mainly syntax but not sure. I have commented the error in the code where it appears.
/**
*This Class makes the variables that define an application within Workflo!Approval
*available as an ApplicationScope variable.
*/
package ca.wfsystems.wfsAppUtils;
import lotus.domino.Base;
import lotus.domino.Session;
import lotus.domino.Database;
import lotus.domino.View;
import lotus.domino.NotesException;
import lotus.domino.ViewColumn;
import lotus.domino.ViewEntry;
import lotus.domino.ViewEntryCollection;
import lotus.domino.Name;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import com.ibm.xsp.extlib.util.ExtLibUtil;
/**
* #author Bill Fox Workflo Systems WFSystems.ca
* July 2014
* This class is provided as part of the Workflo!Approval Product
* and can be reused within this application.
* If copied to a different application please retain this attribution.
*
*/
public abstract class ApplicationUtils implements Serializable, Map<String, Object> {
private static final long serialVersionUID = 1L;
private Session s;
private Name serverName;
private String repID;
private String thisKey;
private ViewEntryCollection formVECol;
private Vector formNames;
private Database thisDB;
private Database appDB;
private View appView;
private View formView;
private ViewEntry formVE;
private ViewEntry tFormVE;
private ViewEntry ve;
private ViewEntry tVE;
private ViewEntryCollection veCol;
private final Map<String, Object> internalMap = new HashMap<String, Object>();
public ApplicationUtils() {
this.populateMap(internalMap);
}
private void populateMap(Map<String, Object> theMap) {
try{
s = ExtLibUtil.getCurrentSession();
//serverName = s.createName(s.getServerName());
thisDB = s.getCurrentDatabase();
appView = thisDB.getView("vwWFSApplications");
veCol = appView.getAllEntries();
ve = veCol.getFirstEntry();
ViewEntry tVE = null;
while (ve != null) {
rtnValue mapValue = new rtnValue();
tVE = veCol.getNextEntry(ve);
Vector colVal = ve.getColumnValues();
thisKey = colVal.get(0).toString();
mapValue.setRepID(colVal.get(2).toString());
// ...... load the rest of the values .......
theMap.put(thisKey, mapValue);
recycleObjects(ve);
ve = tVE;
}
}catch(NotesException e){
System.out.println(e.toString());
}finally{
recycleObjects(ve, veCol, appView, tVE);
}
}
public class rtnValue{
private String RepID;
private String HelpRepID;
private String RuleRepID;
private Vector FormNames;
public String getRepID() {
return RepID;
}
public void setRepID(String repID) {
RepID = repID;
}
public String getHelpRepID() {
return HelpRepID;
}
public void setHelpRepID(String helpRepID) {
HelpRepID = helpRepID;
}
public String getRuleRepID() {
return RuleRepID;
}
public void setRuleRepID(String ruleRepID) {
RuleRepID = ruleRepID;
}
public Vector getFormNames() {
return FormNames;
}
public void setFormNames(Vector formNames) {
FormNames = formNames;
}
}
public void clear() {
this.internalMap.clear();
this.populateMap(this.internalMap);
}
public boolean containsKey(Object key) {
return this.internalMap.containsKey(key);
}
public boolean containsValue(Object value) {
return this.internalMap.containsValue(value);
}
public Set<java.util.Map.Entry<String, Object>> entrySet() {
return this.internalMap.entrySet();
}
public Object get(String key) {
//error on Object get Method must return a result of type Object
try {
if (this.internalMap.containsKey(key)) {
return this.internalMap.get(key);
}
} catch (Exception e) {
System.out.println(e.toString());
rtnValue newMap = new rtnValue();
return newMap;
}
}
public boolean isEmpty() {
return this.internalMap.isEmpty();
}
public Set<String> keySet() {
return this.internalMap.keySet();
}
public Object put(String key, Object value) {
return this.internalMap.put(key, value);
}
public Object remove(Object key) {
return this.internalMap.remove(key);
}
public int size() {
return this.internalMap.size();
}
public Collection<Object> values() {
return this.internalMap.values();
}
public void putAll(Map<? extends String, ? extends Object> m) {
this.internalMap.putAll(m);
}
public String getAppRepID(String key){
/*get the Replica Id of the application database
* not sure this is the correct way to call this
*/
rtnValue mapValue = new rtnValue();
mapValue = this.internalMap.get(key);
//error on line above Type Mismatch: can not convert Object to ApplicationUtils.rtnValue
String repID = mapValue.getRepID();
}
public static void recycleObjects(Object... args) {
for (Object o : args) {
if (o != null) {
if (o instanceof Base) {
try {
((Base) o).recycle();
} catch (Throwable t) {
// who cares?
}
}
}
}
}
}
For the get() method, the way I handle that kind of situation is create a variable of the correct data type as null, in my try/catch set the variable, and at the end return the variable. So:
Object retVal = null;
try....
return retVal;
For the other error, if you right-click on the error marker, it might give you the opportunity to cast the variable to rtnValue, so:
mapValue = (rtnValue) this.internalMap.get(key)
If you haven't got it, Head First Java was a useful book for getting my head around some Java concepts. It's also worth downloading the FindBugs plugin for Domino Designer from OpenNTF. It will identify errors as well as bad practices. Just ignore the errors in the "local" package!
The problem is that there is an execution path that do not return nothing
public Object get(String key) {
//error on Object get Method must return a result of type Object
try {
if (this.internalMap.containsKey(key)) { // false
return this.internalMap.get(key);
}
} catch (Exception e) {
System.out.println(e.toString());
rtnValue newMap = new rtnValue();
return newMap;
}
}
if key is not present in the internalMap, nothing is thrown, then that method do not return anything.
To fix the problem, return the newMap at the end.
public Object get(String key) {
//error on Object get Method must return a result of type Object
try {
if (this.internalMap.containsKey(key)) {
return this.internalMap.get(key);
}
} catch (Exception e) {
System.out.println(e.toString());
}
rtnValue newMap = new rtnValue();
return newMap;
}
You can inline the return to save the allocation (which is what the compiler will do anyway). I didn't do it just to make it clear in the example.
But still you have a compiler error in getAppRepID method. You are expecting a rtnValue but you send back an Object. You must cast there.
The appropriate way to handle this is, if you know that all values are of a given type, create the map with the proper type.
Have you tried making your internalMap a map of rtnValue instances (so )?
I have to following code to check whether the entity in my model has a nullable=false or similar annotation on a field.
import javax.persistence.Column;
import .....
private boolean isRequired(Item item, Object propertyId) {
Class<?> property = getPropertyClass(item, propertyId);
final JoinColumn joinAnnotation = property.getAnnotation(JoinColumn.class);
if (null != joinAnnotation) {
return !joinAnnotation.nullable();
}
final Column columnAnnotation = property.getAnnotation(Column.class);
if (null != columnAnnotation) {
return !columnAnnotation.nullable();
}
....
return false;
}
Here's a snippet from my model.
import javax.persistence.*;
import .....
#Entity
#Table(name="m_contact_details")
public class MContactDetail extends AbstractMasterEntity implements Serializable {
#Column(length=60, nullable=false)
private String address1;
For those people unfamiliar with the #Column annotation, here's the header:
#Target({METHOD, FIELD})
#Retention(RUNTIME)
public #interface Column {
I'd expect the isRequired to return true every now and again, but instead it never does.
I've already done a mvn clean and mvn install on my project, but that does not help.
Q1: What am I doing wrong?
Q2: is there a cleaner way to code isRequired (perhaps making better use of generics)?
property represents a class (it's a Class<?>)
#Column and #JoinColumn can only annotate fields/methods.
Consequently you will never find these annotations on property.
A slightly modified version of your code that prints out whether the email property of the Employee entity is required:
public static void main(String[] args) throws NoSuchFieldException {
System.out.println(isRequired(Employee.class, "email"));
}
private static boolean isRequired(Class<?> entity, String propertyName) throws NoSuchFieldException {
Field property = entity.getDeclaredField(propertyName);
final JoinColumn joinAnnotation = property.getAnnotation(JoinColumn.class);
if (null != joinAnnotation) {
return !joinAnnotation.nullable();
}
final Column columnAnnotation = property.getAnnotation(Column.class);
if (null != columnAnnotation) {
return !columnAnnotation.nullable();
}
return false;
}
Note that this is a half-baked solution, because JPA annotations can either be on a field or on a method. Also be aware of the difference between the reflection methods like getFiled()/getDeclaredField(). The former returns inherited fields too, while the latter returns only fields of the specific class ignoring what's inherited from its parents.
The following code works:
#SuppressWarnings("rawtypes")
private boolean isRequired(BeanItem item, Object propertyId) throws SecurityException {
String fieldname = propertyId.toString();
try {
java.lang.reflect.Field field = item.getBean().getClass().getDeclaredField(fieldname);
final JoinColumn joinAnnotation = field.getAnnotation(JoinColumn.class);
if (null != joinAnnotation) {
return !joinAnnotation.nullable();
}
final Column columnAnnotation = field.getAnnotation(Column.class);
if (null != columnAnnotation) {
return !columnAnnotation.nullable();
}
} catch (NoSuchFieldException e) {
//not a problem no need to log this event.
return false;
}
}
Let's say I have an abstract class:
abstract class Foo extends Bar {
public abstract int foo();
}
that I want to extend at runtime to create a Class object. The hope would be that I could have a dynamically generated class:
class FooImpl extends Foo {
#Override
public int foo() {
return 5;
}
}
that would be represented by a Class object and that I could then use reflection to create new instances of. The key is that I would like to decide the return value of the method foo() at runtime. My thought is to use ASM to create the bytecode for the class and then use reflection on a ClassLoader object to define the Class.
Is using ASM and then reflection of the method ClassLoader#defineClass on the generated bytes the best way to implement abstract methods at runtime with non-hardcoded values?
If yes, how would I go about doing that. My gut is to utilize the ASMifierClassVisitor, but I'm not quite sure on the exact method of doing that. I know that if all else fails I can manually go through the JVM instructions required to define a specific class but I feel there must be an easier way.
If no, what is the best way and how would I go about using the best way?
EDIT: I checked out all of the answers and I decided that none of them were exactly what I was looking for. I ended up creating a small implementation of what I was talking about with ASM. I figured I should post it here:
import org.objectweb.asm.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
/**
* Created by IntelliJ IDEA.
* User: Matt
* Date: 9/17/11
* Time: 12:42 PM
*/
public class OverrideClassAdapter extends ClassAdapter {
private final HashMap<String, Object> code;
private final String className;
private final ClassWriter writer;
private String superName;
public OverrideClassAdapter(ClassWriter writer, String className, Queue<int[]> constructorCode, HashMap<String, Object> code) {
super(writer);
this.writer = writer;
this.className = className;
this.code = code;
}
#Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.superName = name;
if((access & Opcodes.ACC_ABSTRACT) != 0)
access &= ~Opcodes.ACC_ABSTRACT;
if((access & Opcodes.ACC_INTERFACE) != 0)
access &= ~Opcodes.ACC_INTERFACE;
cv.visit(version, access, className, signature, name, null);
}
#Override
public void visitSource(String source, String debug) {
}
#Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
boolean isAbstract = (access & Opcodes.ACC_ABSTRACT) != 0;
if(isAbstract)
access &= ~Opcodes.ACC_ABSTRACT;
MethodWriter mw = (MethodWriter) cv.visitMethod(access, name, desc, signature, exceptions);
Object value = code.get(name);
if(isAbstract || value != null) {
if(value instanceof BytecodeValue) {
BytecodeValue returnableValue = (BytecodeValue) value;
int[] byteCode = new int[returnableValue.getValueCode().length + 1];
System.arraycopy(returnableValue.getValueCode(), 0, byteCode, 0, returnableValue.getValueCode().length);
if(returnableValue.getValueCode().length > 1 && returnableValue.getValueCode()[1] == 0) {
byteCode[1] = writer.newConst(returnableValue.getValue());
}
byteCode[byteCode.length - 1] = returnableValue.getReturnCode();
value = byteCode;
}
return new OverrideMethodAdapter(mw, (int[]) value);
}
return mw;
}
private class OverrideMethodAdapter extends MethodAdapter {
private final int[] code;
private final MethodWriter writer;
public OverrideMethodAdapter(MethodWriter writer, int[] code) {
super(writer);
this.writer = writer;
this.code = code;
}
#Override
public void visitEnd() {
try {
Field code = MethodWriter.class.getDeclaredField("code");
code.setAccessible(true);
ByteVector bytes = new ByteVector();
for(int b : this.code)
bytes.putByte(b);
code.set(writer, bytes);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static byte[] extendClassBytes(Class clazz, String className, HashMap<String, Object> methodImpls) throws IOException {
ClassReader cr = new ClassReader(clazz.getName());
ClassWriter cw = new ClassWriter(0);
cr.accept(new OverrideClassAdapter(cw, className, methodImpls), ClassReader.SKIP_DEBUG);
cr = new ClassReader(cw.toByteArray());
cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
cr.accept(cw, ClassReader.SKIP_DEBUG);
//CheckClassAdapter.verify(new org.objectweb.asm.ClassReader(cw.toByteArray()), true, new PrintWriter(System.out));
/*File file = new File(className + ".class");
new FileOutputStream(file).write(cw.toByteArray());*/
return cw.toByteArray();
}
public static Class extendClass(Class clazz, String className, HashMap<String, Object> methodImpls) throws IOException {
return defineClass(extendClassBytes(clazz, className, methodImpls), className);
}
public static Class defineClass(byte[] code, String name) {
try {
Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
method.setAccessible(true);
return (Class) method.invoke(Thread.currentThread().getContextClassLoader(), name, code, 0, code.length);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
You might want to look at using CGLib. It can do what Java's dynamic proxies can do but for abstract classes as well as interfaces, and it has a similar API to java.lang.reflect.Proxy for doing this as well. CGLib uses ASM behind the scenes anyway, but by using CGLib you wont have to craft bytecode directly.
Here's an example of how to use CGLib to do this:
package cglibtest;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CGLibTest
{
public static void main(String... args)
{
MyAbstract instance = (MyAbstract)Enhancer.create(MyAbstract.class, new MyInterceptor(42));
System.out.println("Value from instance: " + instance.valueMethod());
}
public static class MyInterceptor implements MethodInterceptor
{
private final Object constantValue;
public MyInterceptor(Object constantValue)
{
this.constantValue = constantValue;
}
#Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable
{
if ("valueMethod".equals(method.getName()))
return(constantValue);
else
return(null);
}
}
public static abstract class MyAbstract
{
public abstract int valueMethod();
}
}
What's stopping you from reading the value 5 from say properties and return it back? That's too simple so, I guess you must have something more complex than returning an int that you want to accomplish here. I agree with the posts above that generating classes at runtime would be very expensive. If you know your business logic in advance, you can apply the Factory pattern to load the desired implementation of defined interfaces at runtime. That's how JDBC libraries work.
If you do not know the business logic in advance and have lot's of it then, you might benefit from using an off the shelf Rule Engine to process the logic and return results back to your Java program. It is much easier to maintain this logic in a Rule Engine specially if it is changing frequently.
Yes, that approach should work. But it will be expensive if you do a lot of class generation. (We are probably talking about hundreds of thousands of instructions to generate the bytecode file and then load it. And then there's the memory need to represent the class when it is loaded.)
Another approach (also expensive) is to generate source code and compile and load it at runtime.
Finally, you should consider the approach of making the logic of the objects table-driven or implementing it using some kind of interpreter. If you actually need to have different classes, you could wrap this up using Java's dynamic proxy class mechanism; e.g. see java.lang.reflect.Proxy