All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
xy.reflect.ui.control.swing.editor.AbstractEditorFormBuilder Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (C) 2018 OTK Software
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* The GNU General Public License allows you also to freely redistribute
* the libraries under the same license, if you provide the terms of the
* GNU General Public License with them and add the following
* copyright notice at the appropriate place (with a link to
* http://javacollection.net/reflectionui/ web site when possible).
******************************************************************************/
package xy.reflect.ui.control.swing.editor;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import xy.reflect.ui.control.IContext;
import xy.reflect.ui.control.swing.renderer.Form;
import xy.reflect.ui.control.swing.renderer.SwingRenderer;
import xy.reflect.ui.info.ValueReturnMode;
import xy.reflect.ui.info.field.IFieldInfo;
import xy.reflect.ui.info.filter.IInfoFilter;
import xy.reflect.ui.info.type.ITypeInfo;
import xy.reflect.ui.info.type.factory.EncapsulatedObjectFactory;
import xy.reflect.ui.info.type.source.ITypeInfoSource;
import xy.reflect.ui.info.type.source.JavaTypeInfoSource;
import xy.reflect.ui.undo.AbstractSimpleModificationListener;
import xy.reflect.ui.undo.SlaveModificationStack;
import xy.reflect.ui.undo.IModification;
import xy.reflect.ui.undo.ModificationStack;
import xy.reflect.ui.util.Accessor;
import xy.reflect.ui.util.ReflectionUIError;
import xy.reflect.ui.util.ReflectionUIUtils;
import xy.reflect.ui.util.SwingRendererUtils;
/**
* This is a base class for form-based editor control factories.
*
* Each instance of this class handles a target value/object according to the
* specifications provided through the implementation of the various class
* methods.
*
* Note that the target value/object is encapsulated in a virtual parent object
* for practical reasons. The editor control is thus a form representing this
* capsule.
*
* This class also handles the complex relationship that may exists between the
* target value/object and a potential parent object. The parent object form
* will typically embed (real-time link) or maintain a detached parent-child
* relationship (e.g.: child dialog) with the target value/object form.
*
* A real-time link to the parent object can be exclusive or not, meaning that
* the target value/object is the only child of its parent object. This
* information is used when forwarding target value/object modifications to the
* parent modification stack. Typically, calling
* {@link ModificationStack#forget()} on the target value/object modification
* stack will trigger the same call on the parent modification stack only and
* only if only the link is exclusive. Otherwise
* {@link ModificationStack#invalidate()} will be called instead.
*
* @author olitank
*
*/
public abstract class AbstractEditorFormBuilder {
protected Object initialObjectValue;
protected boolean objectValueInitialized = false;
protected boolean objectValueReplaced = false;
protected Accessor encapsulatedObjectValueAccessor;
/**
* @return the renderer used to create the target value/object form(s).
*/
public abstract SwingRenderer getSwingRenderer();
/**
* @return the parent object modification stack or null (if there is no parent
* object).
*/
public abstract ModificationStack getParentModificationStack();
/**
* @return the title or the title prefix (in case of multiple modifications) of
* the modification(s) that will be communicated to the parent object
* modification stack or null (if there is no parent object).
*/
public abstract String getParentModificationTitle();
/**
* @return whether modifications of the target value/object can be committed
* (using the result of {@link #createParentCommitModification(Object)})
* to make them real for the parent object.
*/
public abstract boolean canCommitToParent();
/**
* @param newObjectValue
* @return a modification that will be applied in order to make any target
* value/object modification real for the parent object. Typically
* primitive field values would need to be committed (set back) to their
* parent object after modification.
*/
public abstract IModification createParentCommitModification(Object newObjectValue);
/**
* @return source of the declared type information that will be used to handle
* the target value/object. If null is returned then this type
* information source will be dynamically inferred from the target
* value/object. Note that calling
* {@link ITypeInfoSource#getSpecificitiesIdentifier()} on the result
* should return null.
*/
public abstract ITypeInfoSource getDeclaredNonSpecificTypeInfoSource();
/**
* @return the return mode (from the parent object) of the target value/object
* (null can be returned if there is no parent object).
*/
public abstract ValueReturnMode getReturnModeFromParent();
/**
* @return true if and only if the null value can be set and then must be
* distinctly displayed.
*/
public abstract boolean isNullValueDistinct();
/**
* @return the initial target object/value.
*/
public abstract Object getInitialValue();
/**
* @return an object that will be used to uniquely name the capsule type (or
* null).
*/
public abstract IContext getContext();
/**
* @return an secondary object that will be used to uniquely name the capsule
* type (or null).
*/
public abstract IContext getSubContext();
/**
* Ensures that the initial target value/object has been acquired.
*/
protected void ensureIsInitialized() {
if (objectValueInitialized) {
return;
}
encapsulatedObjectValueAccessor = new Accessor() {
Object object = initialObjectValue = getInitialValue();
@Override
public Object get() {
return object;
}
@Override
public void set(Object t) {
object = t;
objectValueReplaced = true;
}
};
objectValueInitialized = true;
}
/**
* @return whether the initial target value/object has been acquired.
*/
public boolean isInitialized() {
return objectValueInitialized;
}
/**
* @return whether the target object/value has been replaced during the lifetime
* of the editor control. Typically immutable objects like primitive
* wrappers would be replaced on every modification.
*/
public boolean isValueReplaced() {
return objectValueReplaced;
}
/**
* @return whether the target object/value can be replaced during the lifetime
* of the editor control.
*/
public boolean canReplaceValue() {
return canCommitToParent();
}
/**
* @return the current target object/value.
*/
public Object getCurrentValue() {
ensureIsInitialized();
return encapsulatedObjectValueAccessor.get();
}
/**
* @return the capsule holding the target value/object.
*/
public Object getCapsule() {
ensureIsInitialized();
return getEncapsulation().getInstance(encapsulatedObjectValueAccessor);
}
/**
* @return the name of the capsule type.
*/
public String getCapsuleTypeName() {
String contextDeclaraion;
{
IContext context = getContext();
if (context == null) {
contextDeclaraion = "";
} else {
contextDeclaraion = "context=" + context.getIdentifier() + ", ";
}
}
String subContextDeclaraion;
{
IContext subContext = getSubContext();
if (subContext == null) {
subContextDeclaraion = "";
} else {
subContextDeclaraion = "subContext=" + subContext.getIdentifier() + ", ";
}
}
return "Encapsulation [" + contextDeclaraion + subContextDeclaraion + "encapsulatedObjectType="
+ getSwingRenderer().getReflectionUI().getTypeInfo(getEncapsulatedFieldNonSpecificTypeSource())
.getName()
+ "]";
}
/**
* @return the target value/object capsule factory.
*/
public EncapsulatedObjectFactory getEncapsulation() {
ITypeInfo fieldType = getSwingRenderer().getReflectionUI()
.getTypeInfo(getEncapsulatedFieldNonSpecificTypeSource());
EncapsulatedObjectFactory result = new EncapsulatedObjectFactory(getSwingRenderer().getReflectionUI(),
getCapsuleTypeName(), fieldType) {
@Override
protected Object[] getFieldValueOptions() {
return getEncapsulatedFieldValueOptions();
}
};
result.setTypeModificationStackAccessible(isCapsuleTypeModificationStackAccessible());
result.setTypeCaption(getCapsuleTypeCaption());
Map typeSpecificProperties = new HashMap();
{
result.setTypeSpecificProperties(typeSpecificProperties);
}
result.setFieldName(getEncapsulatedFieldName());
result.setFieldCaption(getEncapsulatedFieldCaption());
result.setFieldGetOnly(isEncapsulatedFieldGetOnly());
result.setFieldNullValueDistinct(isNullValueDistinct());
result.setFieldValueReturnMode(getEncapsulatedFieldValueReturnMode());
result.setFieldFormControlEmbedded(isEncapsulatedFormExpanded());
result.setFieldFormControlFilter(getEncapsulatedFormFilter());
result.setFieldFormControlMandatory(isCustomEncapsulatedControlForbidden());
result.setFieldSpecificProperties(getEncapsulatedFieldSpecificProperties());
return result;
}
/**
* @return a form filter that will used (in case the target value/object is
* represented by a generic form control).
*/
public abstract IInfoFilter getEncapsulatedFormFilter();
/**
* @return whether the target object/value form is embedded in the editor
* control or displayed in a child dialog. Note that this method has no
* impact in case the target value/object is not represented by a
* generic form control.
*/
public abstract boolean isEncapsulatedFormExpanded();
/**
* @return true if the target value/object is forcibly displayed as a generic
* form (not a custom control).
*/
public boolean isCustomEncapsulatedControlForbidden() {
return false;
}
/**
* @return the name of the encapsulated field that will return target
* value/object.
*/
protected String getEncapsulatedFieldName() {
return "";
}
/**
* @return the specific properties of the encapsulated field information.
*/
protected Map getEncapsulatedFieldSpecificProperties() {
return Collections.emptyMap();
}
/**
* @return the encapsulated field value return mode (equals to
* {@link #getReturnModeFromParent()} is there is a parent object).
*/
protected ValueReturnMode getEncapsulatedFieldValueReturnMode() {
return hasParentObject() ? getReturnModeFromParent() : ValueReturnMode.DIRECT_OR_PROXY;
}
/**
* @return the encapsulated field value options or null.
*/
protected Object[] getEncapsulatedFieldValueOptions() {
return null;
}
/**
* @return whether the encapsulated field is get-only.
*/
protected boolean isEncapsulatedFieldGetOnly() {
return isInReadOnlyMode() || !canReplaceValue();
}
/**
* @return true if and only if the undo/redo/etc features should be made
* available typically when the capsule is the root of a window.
*/
protected boolean isCapsuleTypeModificationStackAccessible() {
return !isInReadOnlyMode();
}
public String getEncapsulatedFieldCaption() {
return "";
}
public ITypeInfoSource getEncapsulatedFieldNonSpecificTypeSource() {
ITypeInfoSource result = getDeclaredNonSpecificTypeInfoSource();
if (result != null) {
return result;
}
ensureIsInitialized();
if (initialObjectValue != null) {
return getSwingRenderer().getReflectionUI().getTypeInfoSource(initialObjectValue);
}
return new JavaTypeInfoSource(Object.class, null);
}
public String getCapsuleTypeCaption() {
return getSwingRenderer().getReflectionUI().getTypeInfo(getEncapsulatedFieldNonSpecificTypeSource())
.getCaption();
}
/**
* @return whether the editor control is refreshed every time a modification of
* the target value/object is detected. It typically allows to keep a
* calculated read-only target value/object coherent by resetting it to
* its initial state whenever its temporary value is modified.
*/
protected boolean isEditorFormRefreshedOnModification() {
return isInReadOnlyMode();
}
/**
* @return whether the target value/object modifications have no impact on the
* parent object. If there is no parent object then false should be
* returned.
*/
protected boolean isInReadOnlyMode() {
return hasParentObject() ? !canPotentiallyModifyParentObject() : false;
}
/**
* @return whether the editor control is expected to be empty or not.
*/
public boolean isFormEmpty() {
Object capsule = getCapsule();
ITypeInfo capsuleType = getSwingRenderer().getReflectionUI()
.getTypeInfo(getSwingRenderer().getReflectionUI().getTypeInfoSource(capsule));
IFieldInfo encapsulatedField = capsuleType.getFields().get(0);
if (encapsulatedField.isNullValueDistinct()) {
return false;
}
Object object = getCurrentValue();
if (object == null) {
return false;
}
ITypeInfo actualObjectType = getSwingRenderer().getReflectionUI()
.getTypeInfo(getSwingRenderer().getReflectionUI().getTypeInfoSource(object));
if (!SwingRendererUtils.isFormEmpty(actualObjectType, getEncapsulatedFormFilter(), getSwingRenderer())) {
return false;
}
return true;
}
/**
* Creates and return the editor control.
*
* @param realTimeLinkWithParent
* Whether a real-time link should be maintained with the parent
* object.
* @param exclusiveLinkWithParent
* Whether the real-time link with the parent object (if existing) is
* exclusive or not.
* @return the created editor control.
*/
public Form createEditorForm(boolean realTimeLinkWithParent, boolean exclusiveLinkWithParent) {
Object encapsulated = getCapsule();
Form result = getSwingRenderer().createForm(encapsulated);
setupLinkWithParent(result, realTimeLinkWithParent, exclusiveLinkWithParent);
return result;
}
/**
* Installs the link between the target value/object editor control and its
* parent object form.
*
* @param editorForm
* The created target value/object editor control.
* @param realTimeLinkWithParent
* Whether a real-time link should be maintained with the parent
* object.
* @param exclusiveLinkWithParent
* Whether the real-time link with the parent object (if existing) is
* exclusive or not.
*/
protected void setupLinkWithParent(Form editorForm, boolean realTimeLinkWithParent,
boolean exclusiveLinkWithParent) {
if (realTimeLinkWithParent) {
if (canPotentiallyModifyParentObject()) {
forwardEditorFormModificationsToParentObject(editorForm, exclusiveLinkWithParent);
}
if (isEditorFormRefreshedOnModification()) {
refreshEditorFormOnModification(editorForm);
}
}
}
/**
* @return whether the target value/object has a parent object or not.
*/
protected boolean hasParentObject() {
return getParentModificationStack() != null;
}
/**
* @return whether modifications of the target value/object can impact the
* parent object.
*/
public boolean canPotentiallyModifyParentObject() {
if (!hasParentObject()) {
return false;
}
ensureIsInitialized();
return ReflectionUIUtils.canEditSeparateObjectValue(
ReflectionUIUtils.isValueImmutable(getSwingRenderer().getReflectionUI(), initialObjectValue),
getReturnModeFromParent(), canCommitToParent());
}
/**
* Installs a listener that will trigger the editor control refreshing whenever
* a modification of the target value/object is detected.
*
* @param editorForm
* The created editor control.
*/
protected void refreshEditorFormOnModification(final Form editorForm) {
ModificationStack childModificationStack = editorForm.getModificationStack();
childModificationStack.addListener(new AbstractSimpleModificationListener() {
@Override
protected void handleAnyEvent(IModification modification) {
refreshEditorForm(editorForm, false);
}
});
}
/**
* Refreshes the editor control.
*
* @param editorForm
* The created editor control.
* @param refreshStructure
* Whether the editor control should update its structure to reflect
* the recent meta-data change. Mainly used in design mode.
*/
public void refreshEditorForm(Form editorForm, boolean refreshStructure) {
encapsulatedObjectValueAccessor.set(getInitialValue());
editorForm.refresh(refreshStructure);
}
/**
* @param value
* The new target value.
* @return whether the new target value passed as argument was accepted or
* rejected, typically by a user.
*/
protected boolean shouldAcceptNewObjectValue(Object value) {
return true;
}
/**
* Installs on the editor control a listener that will forward the target
* value/object modifications to the parent object modification stack.
*
* @param editorForm
* The created editor control.
* @param exclusiveLinkWithParent
* Whether the real-time link with the parent object (if existing) is
* exclusive or not.
*/
protected void forwardEditorFormModificationsToParentObject(final Form editorForm,
boolean exclusiveLinkWithParent) {
Accessor childModifAcceptedGetter = new Accessor() {
@Override
public Boolean get() {
return shouldAcceptNewObjectValue(getCurrentValue());
}
};
Accessor childValueReturnModeGetter = new Accessor() {
@Override
public ValueReturnMode get() {
return getReturnModeFromParent();
}
};
Accessor childValueReplacedGetter = new Accessor() {
@Override
public Boolean get() {
return (isValueReplaced());
}
};
Accessor commitModifGetter = new Accessor() {
@Override
public IModification get() {
if (!canCommitToParent()) {
return null;
}
return createParentCommitModification(getCurrentValue());
}
};
Accessor childModifTitleGetter = new Accessor() {
@Override
public String get() {
return getParentModificationTitle();
}
};
Accessor masterModifStackGetter = new Accessor() {
@Override
public ModificationStack get() {
ModificationStack result = getParentModificationStack();
if (result == null) {
throw new ReflectionUIError();
}
return result;
}
};
editorForm.setModificationStack(new SlaveModificationStack(getSwingRenderer(), editorForm,
childModifAcceptedGetter, childValueReturnModeGetter, childValueReplacedGetter, commitModifGetter,
childModifTitleGetter, masterModifStackGetter, exclusiveLinkWithParent));
}
}