/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jkiss.dbeaver.runtime.properties;
import org.eclipse.core.runtime.Platform;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.DBPPersistedObject;
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
import org.jkiss.dbeaver.model.preferences.DBPPropertySource;
import org.jkiss.dbeaver.model.meta.IPropertyValueListProvider;
import org.jkiss.dbeaver.model.meta.IPropertyValueTransformer;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
import org.jkiss.utils.BeanUtils;
import org.jkiss.utils.CommonUtils;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.DecimalFormat;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.ResourceBundle;
/**
* ObjectPropertyDescriptor
*/
public class ObjectPropertyDescriptor extends ObjectAttributeDescriptor implements DBPPropertyDescriptor, IPropertyValueListProvider<Object>
{
private final Property propInfo;
private final String propName;
private final String propDescription;
private Method setter;
private IPropertyValueTransformer valueTransformer;
private IPropertyValueTransformer valueRenderer;
private final Class<?> declaringClass;
private Format displayFormat = null;
public ObjectPropertyDescriptor(
DBPPropertySource source,
ObjectPropertyGroupDescriptor parent,
Property propInfo,
Method getter)
{
super(source, parent, getter, propInfo.id(), propInfo.order());
this.propInfo = propInfo;
final String propertyName = BeanUtils.getPropertyNameFromGetter(getter.getName());
declaringClass = getter.getDeclaringClass();
Class<?> c = declaringClass;
while (setter == null && c != Object.class && c != null) {
this.setter = BeanUtils.getSetMethod(
c,
propertyName);
if (setter == null) {
c = c.getSuperclass();
}
}
// Obtain value transformer
Class<? extends IPropertyValueTransformer> valueTransformerClass = propInfo.valueTransformer();
if (valueTransformerClass != null && valueTransformerClass != IPropertyValueTransformer.class) {
try {
valueTransformer = valueTransformerClass.newInstance();
} catch (Throwable e) {
log.warn("Can't create value transformer", e);
}
}
// Obtain value transformer
Class<? extends IPropertyValueTransformer> valueRendererClass = propInfo.valueRenderer();
if (valueRendererClass != null && valueRendererClass != IPropertyValueTransformer.class) {
try {
valueRenderer = valueRendererClass.newInstance();
} catch (Throwable e) {
log.warn("Can't create value renderer", e);
}
}
this.propName = propInfo.hidden() ? getId() : getLocalizedString(propInfo.name(), Property.RESOURCE_TYPE_NAME, getId());
this.propDescription = CommonUtils.isEmpty(propInfo.description()) ?
propName :
getLocalizedString(propInfo.name(), Property.RESOURCE_TYPE_DESCRIPTION, propName);
}
@Override
public Class<?> getDeclaringClass()
{
return declaringClass;
}
public boolean isViewable()
{
return propInfo.viewable() && !propInfo.hidden();
}
public boolean isHidden()
{
return propInfo.hidden();
}
public boolean isExpensive()
{
return propInfo.expensive();
}
public boolean isNumeric()
{
Class<?> propType = getGetter().getReturnType();
return propType != null && BeanUtils.isNumericType(propType);
}
public boolean isDateTime()
{
Class<?> propType = getGetter().getReturnType();
return propType != null && Date.class.isAssignableFrom(propType);
}
public boolean supportsPreview()
{
return propInfo.supportsPreview();
}
public IPropertyValueTransformer getValueTransformer()
{
return valueTransformer;
}
public IPropertyValueTransformer getValueRenderer() {
return valueRenderer;
}
@Override
public boolean isEditable(Object object)
{
final DBPPropertySource propertySource = getSource();
if (!(propertySource instanceof IPropertySourceEditable) || !((IPropertySourceEditable) propertySource).isEditable(object)) {
return false;
}
// Read-only or non-updatable property for non-new object
return isNewObject(object) ? propInfo.editable() : propInfo.updatable();
}
public boolean isEditPossible()
{
return propInfo.editable();
}
private boolean isNewObject(Object object)
{
return object instanceof DBPPersistedObject && !((DBPPersistedObject) object).isPersisted();
}
@Override
public String getCategory()
{
return CommonUtils.isEmpty(propInfo.category()) ? null : propInfo.category();
}
@Override
public String getDescription()
{
return propDescription;
}
@NotNull
@Override
public String getDisplayName()
{
return propName;
}
public Format getDisplayFormat() {
if (displayFormat == null) {
final String format = propInfo.format();
if (format == null || format.isEmpty()) {
return null;
}
if (isNumeric()) {
displayFormat = new DecimalFormat(format);
} else if (isDateTime()) {
displayFormat = new SimpleDateFormat(format);
} else {
log.debug("Don't know how to apply format to property " + getId());
displayFormat = null;
}
}
return displayFormat;
}
public Object readValue(Object object, @Nullable DBRProgressMonitor progressMonitor)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
{
if (object == null) {
return null;
}
Object value;
if (getParent() != null) {
object = getParent().getGroupObject(object, progressMonitor);
if (object == null) {
return null;
}
}
if (isLazy()) {
// Lazy (probably cached)
if (isLazy(object, true) && progressMonitor == null && !supportsPreview()) {
throw new IllegalAccessException("Lazy property can't be read with null progress monitor");
}
value = getGetter().invoke(object, progressMonitor);
} else {
value = getGetter().invoke(object);
}
if (valueRenderer != null) {
value = valueRenderer.transform(object, value);
}
if (value instanceof Number) {
final Format displayFormat = getDisplayFormat();
if (displayFormat != null) {
return displayFormat.format(value);
}
}
return value;
}
public void writeValue(Object object, Object value)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
{
if (setter != null) {
// Check for null
if (value == null) {
Annotation[] valueAnnotations = setter.getParameterAnnotations()[0];
for (Annotation va : valueAnnotations) {
if (va.annotationType() == NotNull.class) {
throw new IllegalArgumentException("Property '" + getId() + "' can't be set into NULL");
}
}
}
if (getParent() != null) {
// Use void monitor because this object already read by readValue
object = getParent().getGroupObject(object, new VoidProgressMonitor());
}
if (value == null) {
// Check for primitive argument
final Class<?> argType = setter.getParameterTypes()[0];
if (argType == Integer.TYPE) {
value = 0;
} else if (argType == Short.TYPE) {
value = (short)0;
} else if (argType == Long.TYPE) {
value = 0l;
} else if (argType == Float.TYPE) {
value = (float)0.0;
} else if (argType == Double.TYPE) {
value = 0.0;
} else if (argType == Boolean.TYPE) {
value = false;
} else if (argType == Character.TYPE) {
value = ' ';
}
}
setter.invoke(object, value);
} else {
throw new IllegalAccessError("No setter found for property " + getId());
}
}
@Override
public String toString()
{
return getId() + " (" + propInfo.name() + ")";
}
@Override
public Class<?> getDataType()
{
return getGetter().getReturnType();
}
@Override
public boolean isRequired()
{
return false;
}
@Override
public Object getDefaultValue()
{
return null;
}
@Override
public boolean allowCustomValue()
{
if (propInfo.listProvider() != IPropertyValueListProvider.class) {
// List
try {
return propInfo.listProvider().newInstance().allowCustomValue();
} catch (Exception e) {
log.error(e);
}
}
return false;
}
@Override
public Object[] getPossibleValues(Object object)
{
if (propInfo.listProvider() != IPropertyValueListProvider.class) {
// List
try {
return propInfo.listProvider().newInstance().getPossibleValues(object);
} catch (Exception e) {
log.error(e);
}
} else if (getDataType().isEnum()) {
return getDataType().getEnumConstants();
}
return null;
}
@Override
public int hashCode()
{
return propInfo.hashCode();
}
@Override
public boolean equals(Object obj)
{
return obj instanceof ObjectPropertyDescriptor &&
propInfo.equals(((ObjectPropertyDescriptor)obj).propInfo) &&
CommonUtils.equalObjects(getGetter(), ((ObjectPropertyDescriptor)obj).getGetter());
}
private String getLocalizedString(String string, String type, String defaultValue) {
if (Property.DEFAULT_LOCAL_STRING.equals(string)) {
Method getter = getGetter();
String propertyName = BeanUtils.getPropertyNameFromGetter(getter.getName());
Class<?> propOwner = getter.getDeclaringClass();
Bundle bundle = FrameworkUtil.getBundle(propOwner);
ResourceBundle resourceBundle = Platform.getResourceBundle(bundle);
String messageID = "meta." + propOwner.getName() + "." + propertyName + "." + type;
String result = null;
try {
result = resourceBundle.getString(messageID);
} catch (Exception e) {
// Try to find the same property in parent classes
for (Class parent = getter.getDeclaringClass().getSuperclass(); parent != null && parent != Object.class; parent = parent.getSuperclass()) {
try {
Method parentGetter = parent.getMethod(getter.getName(), getter.getParameterTypes());
Class<?> parentOwner = parentGetter.getDeclaringClass();
Bundle parentBundle = FrameworkUtil.getBundle(parentOwner);
if (parentBundle == null || parentBundle == bundle) {
continue;
}
ResourceBundle parentResourceBundle = Platform.getResourceBundle(parentBundle);
messageID = "meta." + parentOwner.getName() + "." + propertyName + "." + type;
try {
result = parentResourceBundle.getString(messageID);
break;
} catch (Exception e1) {
// Just skip it
}
} catch (NoSuchMethodException e1) {
// Just skip it
}
}
if (result == null) {
if (type.equals(Property.RESOURCE_TYPE_NAME)) {
log.debug("Resource '" + messageID + "' not found in bundle " + bundle.getSymbolicName());
}
return defaultValue;
}
}
if (!result.equals(messageID)) {
return result;
}
return defaultValue;
}
return string;
}
}