package com.psddev.cms.db;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import com.psddev.dari.db.DatabaseEnvironment;
import com.psddev.dari.db.Modification;
import com.psddev.dari.db.ObjectField;
import com.psddev.dari.db.ObjectType;
import com.psddev.dari.db.Reference;
import com.psddev.dari.util.ObjectUtils;
import com.psddev.dari.util.StringUtils;
import com.psddev.dari.util.TypeDefinition;
/** Controls the tool UI display. */
@ToolUi.FieldInternalNamePrefix("cms.ui.")
@Modification.Classes({ ObjectField.class, ObjectType.class })
public class ToolUi extends Modification<Object> {
private Boolean bulkUpload;
private String codeType;
private Boolean colorPicker;
private String cssClass;
private boolean displayFirst;
private boolean displayLast;
private boolean dropDown;
private Boolean expanded;
private Boolean filterable;
private boolean globalFilter;
private String heading;
private Boolean hidden;
private String iconName;
private String inputProcessorApplication;
private String inputProcessorPath;
private String inputSearcherPath;
private String storagePreviewProcessorApplication;
private String storagePreviewProcessorPath;
private String noteHtml;
private String noteRendererClassName;
private String placeholder;
private String placeholderDynamicText;
private Boolean placeholderEditable;
private Boolean referenceable;
private String referenceableViaClassName;
private Boolean readOnly;
private boolean richText;
private boolean secret;
private Boolean sortable;
private Set<String> standardImageSizes;
private Boolean suggestions;
private Number suggestedMaximum;
private Number suggestedMinimum;
private String tab;
private String storageSetting;
private String defaultSortField;
public boolean isBulkUpload() {
return Boolean.TRUE.equals(bulkUpload);
}
public void setBulkUpload(boolean bulkUpload) {
this.bulkUpload = bulkUpload ? Boolean.TRUE : null;
}
public String getCodeType() {
return codeType;
}
public void setCodeType(String codeType) {
this.codeType = codeType;
}
public boolean isColorPicker() {
return Boolean.TRUE.equals(colorPicker);
}
public void setColorPicker(boolean colorPicker) {
this.colorPicker = colorPicker ? Boolean.TRUE : null;
}
public String getCssClass() {
return cssClass;
}
public void setCssClass(String cssClass) {
this.cssClass = cssClass;
}
public boolean isDisplayFirst() {
return displayFirst;
}
public void setDisplayFirst(boolean displayFirst) {
this.displayFirst = displayFirst;
}
public boolean isDisplayLast() {
return displayLast;
}
public void setDisplayLast(boolean displayLast) {
this.displayLast = displayLast;
}
public boolean isDropDown() {
return dropDown;
}
public void setDropDown(boolean dropDown) {
this.dropDown = dropDown;
}
public boolean isExpanded() {
return Boolean.TRUE.equals(expanded);
}
public void setExpanded(boolean expanded) {
this.expanded = expanded ? Boolean.TRUE : null;
}
public Boolean getFilterable() {
return filterable;
}
public void setFilterable(Boolean filterable) {
this.filterable = filterable;
}
public boolean isEffectivelyFilterable() {
Boolean filterable = getFilterable();
if (filterable != null) {
return filterable;
}
Object object = getOriginalObject();
if (!(object instanceof ObjectField)) {
return false;
}
ObjectField field = (ObjectField) object;
if (field.isDeprecated() ||
isHidden() ||
!ObjectField.RECORD_TYPE.equals(field.getInternalItemType()) ||
field.isEmbedded()) {
return false;
}
for (ObjectType type : field.getTypes()) {
if (type.isEmbedded()) {
return false;
}
}
return true;
}
public boolean isGlobalFilter() {
return globalFilter;
}
public void setGlobalFilter(boolean globalFilter) {
this.globalFilter = globalFilter;
}
/** Returns the heading to display before this object. */
public String getHeading() {
return heading;
}
/** Sets the heading to display before this object. */
public void setHeading(String heading) {
this.heading = heading;
}
public boolean isHidden() {
if (hidden == null) {
hidden = ObjectUtils.to(Boolean.class, getState().get("cms.ui.isHidden"));
}
return hidden != null ? hidden : false;
}
public void setHidden(boolean hidden) {
this.hidden = hidden;
}
public String getIconName() {
return iconName;
}
public String getIconNameOrDefault(String defaultIconName) {
String iconName = getIconName();
return ObjectUtils.isBlank(iconName) ? defaultIconName : iconName;
}
public void setIconName(String iconName) {
this.iconName = iconName;
}
public String getInputProcessorApplication() {
return inputProcessorApplication;
}
public void setInputProcessorApplication(String inputProcessorApplication) {
this.inputProcessorApplication = inputProcessorApplication;
}
public String getInputProcessorPath() {
return inputProcessorPath;
}
public void setInputProcessorPath(String inputProcessorPath) {
this.inputProcessorPath = inputProcessorPath;
}
public String getInputSearcherPath() {
return inputSearcherPath;
}
public void setInputSearcherPath(String inputSearcherPath) {
this.inputSearcherPath = inputSearcherPath;
}
public String getStoragePreviewProcessorPath() {
return storagePreviewProcessorPath;
}
public void setStoragePreviewProcessorPath(String storagePreviewProcessorPath) {
this.storagePreviewProcessorPath = storagePreviewProcessorPath;
}
public String getStoragePreviewProcessorApplication() {
return storagePreviewProcessorApplication;
}
public void setStoragePreviewProcessorApplication(String storagePreviewProcessorApplication) {
this.storagePreviewProcessorApplication = storagePreviewProcessorApplication;
}
public String getNoteHtml() {
if (noteHtml == null) {
setNoteHtml(StringUtils.escapeHtml(ObjectUtils.to(String.class, getState().get("cms.ui.note"))));
}
return noteHtml;
}
public void setNoteHtml(String noteHtml) {
this.noteHtml = noteHtml;
}
@SuppressWarnings("unchecked")
public Class<? extends NoteRenderer> getNoteRendererClass() {
Class<?> c = ObjectUtils.getClassByName(noteRendererClassName);
return c != null && NoteRenderer.class.isAssignableFrom(c) ? (Class<? extends NoteRenderer>) c : null;
}
public void setNoteRendererClass(Class<? extends NoteRenderer> noteRendererClass) {
this.noteRendererClassName = noteRendererClass != null ? noteRendererClass.getName() : null;
}
public String getEffectiveNoteHtml(Object object) {
Class<? extends NoteRenderer> noteRendererClass = getNoteRendererClass();
if (noteRendererClass == null) {
return getNoteHtml();
} else {
NoteRenderer renderer = TypeDefinition.getInstance(noteRendererClass).newInstance();
return renderer.render(object);
}
}
public String getPlaceholder() {
return placeholder;
}
public void setPlaceholder(String placeholder) {
this.placeholder = placeholder;
}
public boolean isReadOnly() {
if (readOnly == null) {
readOnly = ObjectUtils.to(Boolean.class, getState().get("cms.ui.isReadOnly"));
}
return readOnly != null ? readOnly : false;
}
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
public boolean isPlaceholderEditable() {
return Boolean.TRUE.equals(placeholderEditable);
}
public void setPlaceholderEditable(boolean placeholderEditable) {
this.placeholderEditable = Boolean.TRUE.equals(placeholderEditable) ? Boolean.TRUE : null;
}
public String getPlaceholderDynamicText() {
return placeholderDynamicText;
}
public void setPlaceholderDynamicText(String placeholderDynamicText) {
this.placeholderDynamicText = placeholderDynamicText;
}
public boolean isRichText() {
return richText;
}
public void setRichText(boolean richText) {
this.richText = richText;
}
public boolean isReferenceable() {
if (referenceable == null) {
referenceable = ObjectUtils.to(Boolean.class, getState().get("cms.ui.isReferenceable"));
}
return referenceable != null ? referenceable : false;
}
public void setReferenceable(boolean referenceable) {
this.referenceable = referenceable;
}
@SuppressWarnings("unchecked")
public Class<? extends Reference> getReferenceableViaClass() {
Class<?> c = ObjectUtils.getClassByName(referenceableViaClassName);
return c != null && Reference.class.isAssignableFrom(c) ? (Class<? extends Reference>) c : null;
}
public void setReferenceableViaClass(Class<? extends Reference> referenceableViaClass) {
this.referenceableViaClassName = referenceableViaClass != null ? referenceableViaClass.getName() : null;
}
public boolean isSecret() {
return secret;
}
public void setSecret(boolean secret) {
this.secret = secret;
}
public Boolean getSortable() {
return sortable;
}
public void setSortable(Boolean sortable) {
this.sortable = sortable;
}
public boolean isEffectivelySortable() {
Boolean sortable = getSortable();
if (sortable != null) {
return sortable;
}
Object object = getOriginalObject();
if (!(object instanceof ObjectField)) {
return false;
}
ObjectField field = (ObjectField) object;
if (isHidden()) {
return false;
}
String fieldType = field.getInternalType();
return ObjectField.DATE_TYPE.equals(fieldType) ||
ObjectField.NUMBER_TYPE.equals(fieldType) ||
ObjectField.TEXT_TYPE.equals(fieldType) ||
field.isMetric();
}
public Set<String> getStandardImageSizes() {
if (standardImageSizes == null) {
standardImageSizes = new LinkedHashSet<String>();
}
return standardImageSizes;
}
public void setStandardImageSizes(Set<String> standardImageSizes) {
this.standardImageSizes = standardImageSizes;
}
public Boolean getSuggestions() {
return suggestions;
}
public boolean isEffectivelySuggestions() {
return !Boolean.FALSE.equals(suggestions);
}
public void setSuggestions(Boolean suggestions) {
this.suggestions = suggestions;
}
public Number getSuggestedMaximum() {
return suggestedMaximum;
}
public void setSuggestedMaximum(Number suggestedMaximum) {
this.suggestedMaximum = suggestedMaximum;
}
public Number getSuggestedMinimum() {
return suggestedMinimum;
}
public void setSuggestedMinimum(Number suggestedMinimum) {
this.suggestedMinimum = suggestedMinimum;
}
public String getTab() {
return tab;
}
public void setTab(String tab) {
this.tab = tab;
}
public String getStorageSetting() {
return storageSetting;
}
public void setStorageSetting(String storageSetting) {
this.storageSetting = storageSetting;
}
public String getDefaultSortField() {
return defaultSortField;
}
public void setDefaultSortField(String defaultSortField) {
this.defaultSortField = defaultSortField;
}
/**
* Finds a list of all concrete types that can be displayed in the
* context of this type or field.
*
* @return Never {@code null}.
*/
public List<ObjectType> findDisplayTypes() {
Object object = getOriginalObject();
List<ObjectType> displayTypes = new ArrayList<ObjectType>();
Set<ObjectType> concreteTypes;
if (object instanceof ObjectType) {
concreteTypes = ((ObjectType) object).findConcreteTypes();
} else if (object instanceof ObjectField) {
concreteTypes = ((ObjectField) object).findConcreteTypes();
} else {
concreteTypes = null;
}
if (concreteTypes != null) {
for (ObjectType t : concreteTypes) {
if (t.getObjectClass() != null &&
!t.as(ToolUi.class).isHidden()) {
displayTypes.add(t);
}
}
}
return displayTypes;
}
/**
* Specifies whether the target field should enable and accept files
* from the bulk upload feature.
*/
@Documented
@ObjectField.AnnotationProcessorClass(BulkUploadProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BulkUpload {
boolean value() default true;
}
private static class BulkUploadProcessor implements ObjectField.AnnotationProcessor<BulkUpload> {
@Override
public void process(ObjectType type, ObjectField field, BulkUpload annotation) {
field.as(ToolUi.class).setBulkUpload(annotation.value());
}
}
/**
* Specifies that the target field should be displayed as code of the
* given type {@code value}.
*/
@Documented
@ObjectField.AnnotationProcessorClass(CodeTypeProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface CodeType {
String value();
}
private static class CodeTypeProcessor implements ObjectField.AnnotationProcessor<CodeType> {
@Override
public void process(ObjectType type, ObjectField field, CodeType annotation) {
field.as(ToolUi.class).setCodeType(annotation.value());
}
}
/**
* Specifies whether the target field should display the color picker.
*/
@Documented
@ObjectField.AnnotationProcessorClass(ColorPickerProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ColorPicker {
boolean value() default true;
}
private static class ColorPickerProcessor implements ObjectField.AnnotationProcessor<ColorPicker> {
@Override
public void process(ObjectType type, ObjectField field, ColorPicker annotation) {
field.as(ToolUi.class).setColorPicker(annotation.value());
}
}
/**
* Specifies the CSS class to add to {@code .inputContainer} when
* displaying the target field.
*/
@Documented
@ObjectField.AnnotationProcessorClass(CssClassProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface CssClass {
String value();
}
private static class CssClassProcessor implements ObjectField.AnnotationProcessor<CssClass> {
@Override
public void process(ObjectType type, ObjectField field, CssClass annotation) {
field.as(ToolUi.class).setCssClass(annotation.value());
}
}
/**
* Specifies that the target field should be displayed before any other
* fields.
*/
@Documented
@ObjectField.AnnotationProcessorClass(DisplayFirstProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface DisplayFirst {
boolean value() default true;
}
private static class DisplayFirstProcessor implements ObjectField.AnnotationProcessor<DisplayFirst> {
@Override
public void process(ObjectType type, ObjectField field, DisplayFirst annotation) {
field.as(ToolUi.class).setDisplayFirst(annotation.value());
}
}
/**
* Specifies that the target field should be displayed after all other
* fields.
*/
@Documented
@ObjectField.AnnotationProcessorClass(DisplayLastProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface DisplayLast {
boolean value() default true;
}
private static class DisplayLastProcessor implements ObjectField.AnnotationProcessor<DisplayLast> {
@Override
public void process(ObjectType type, ObjectField field, DisplayLast annotation) {
field.as(ToolUi.class).setDisplayLast(annotation.value());
}
}
/**
* Specifies whether the target field should be displayed as a drop-down
* menu.
*/
@Documented
@ObjectField.AnnotationProcessorClass(DropDownProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface DropDown {
boolean value() default true;
}
private static class DropDownProcessor implements ObjectField.AnnotationProcessor<DropDown> {
@Override
public void process(ObjectType type, ObjectField field, DropDown annotation) {
field.as(ToolUi.class).setDropDown(annotation.value());
}
}
/**
* Specifies whether the target field should always be expanded in an
* embedded display.
*/
@Documented
@ObjectField.AnnotationProcessorClass(ExpandedProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Expanded {
boolean value() default true;
}
private static class ExpandedProcessor implements ObjectField.AnnotationProcessor<Expanded> {
@Override
public void process(ObjectType type, ObjectField field, Expanded annotation) {
field.as(ToolUi.class).setExpanded(annotation.value());
}
}
/**
* Specifies whether the target field should be offered as a filterable
* field in search.
*/
@Documented
@ObjectField.AnnotationProcessorClass(FilterableProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Filterable {
boolean value() default true;
}
private static class FilterableProcessor implements ObjectField.AnnotationProcessor<Filterable> {
@Override
public void process(ObjectType type, ObjectField field, Filterable annotation) {
field.as(ToolUi.class).setFilterable(annotation.value());
}
}
/**
* Specifies whether the target type shows up as a filter that can be
* applied to any types in search.
*/
@Documented
@Inherited
@ObjectType.AnnotationProcessorClass(GlobalFilterProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface GlobalFilter {
boolean value() default true;
}
private static class GlobalFilterProcessor implements ObjectType.AnnotationProcessor<GlobalFilter> {
@Override
public void process(ObjectType type, GlobalFilter annotation) {
type.as(ToolUi.class).setGlobalFilter(annotation.value());
}
}
/** Specifies the text to display before the target field. */
@Documented
@Inherited
@ObjectField.AnnotationProcessorClass(HeadingProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Heading {
String value();
}
private static class HeadingProcessor implements ObjectField.AnnotationProcessor<Heading> {
@Override
public void process(ObjectType type, ObjectField field, Heading annotation) {
field.as(ToolUi.class).setHeading(annotation.value());
}
}
/** Specifies whether the target is hidden in the UI. */
@Documented
@Inherited
@ObjectField.AnnotationProcessorClass(HiddenProcessor.class)
@ObjectType.AnnotationProcessorClass(HiddenProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.TYPE })
public @interface Hidden {
boolean value() default true;
}
private static class HiddenProcessor implements
ObjectField.AnnotationProcessor<Hidden>,
ObjectType.AnnotationProcessor<Hidden> {
@Override
public void process(ObjectType type, ObjectField field, Hidden annotation) {
field.as(ToolUi.class).setHidden(annotation.value());
}
@Override
public void process(ObjectType type, Hidden annotation) {
type.as(ToolUi.class).setHidden(annotation.value());
}
}
/** Specifies the name of the icon that represents the target type. */
@Documented
@Inherited
@ObjectType.AnnotationProcessorClass(IconNameProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface IconName {
String value();
}
private static class IconNameProcessor implements ObjectType.AnnotationProcessor<IconName> {
@Override
public void process(ObjectType type, IconName annotation) {
type.as(ToolUi.class).setIconName(annotation.value());
}
}
/**
* Specifies the path to the processor used to render and update
* the target field.
*/
@Documented
@Inherited
@ObjectField.AnnotationProcessorClass(InputProcessorPathProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface InputProcessorPath {
String application() default "";
String value();
}
private static class InputProcessorPathProcessor implements ObjectField.AnnotationProcessor<InputProcessorPath> {
@Override
public void process(ObjectType type, ObjectField field, InputProcessorPath annotation) {
field.as(ToolUi.class).setInputProcessorApplication(annotation.application());
field.as(ToolUi.class).setInputProcessorPath(annotation.value());
}
}
/**
* Specifies the path to the processor used to render previews of StorageItems fields.
*/
@Documented
@Inherited
@ObjectField.AnnotationProcessorClass(StoragePreviewProcessorPathProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface StoragePreviewProcessorPath {
String application() default "";
String value();
}
private static class StoragePreviewProcessorPathProcessor implements ObjectField.AnnotationProcessor<StoragePreviewProcessorPath> {
@Override
public void process(ObjectType type, ObjectField field, StoragePreviewProcessorPath annotation) {
field.as(ToolUi.class).setStoragePreviewProcessorApplication(annotation.application());
field.as(ToolUi.class).setStoragePreviewProcessorPath(annotation.value());
}
}
/**
* Specifies the path to the searcher used to find a value for
* the target field.
*/
@Documented
@Inherited
@ObjectField.AnnotationProcessorClass(InputSearcherPathProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface InputSearcherPath {
String value();
}
private static class InputSearcherPathProcessor implements ObjectField.AnnotationProcessor<InputSearcherPath> {
@Override
public void process(ObjectType type, ObjectField field, InputSearcherPath annotation) {
field.as(ToolUi.class).setInputSearcherPath(annotation.value());
}
}
/** Specifies the note displayed along with the target in the UI. */
@Documented
@Inherited
@ObjectField.AnnotationProcessorClass(NoteProcessor.class)
@ObjectType.AnnotationProcessorClass(NoteProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.TYPE })
public @interface Note {
String value();
}
private static class NoteProcessor implements
ObjectField.AnnotationProcessor<Note>,
ObjectType.AnnotationProcessor<Note> {
@Override
public void process(ObjectType type, ObjectField field, Note annotation) {
field.as(ToolUi.class).setNoteHtml(StringUtils.escapeHtml(annotation.value()));
}
@Override
public void process(ObjectType type, Note annotation) {
type.as(ToolUi.class).setNoteHtml(StringUtils.escapeHtml(annotation.value()));
}
}
/** Renders the note displayed along with a type or a field. */
public static interface NoteRenderer {
/** Renders the note for the given {@code object}. */
public String render(Object object);
}
/**
* Specifies the class that can render the note displayed along with
* the target in the UI.
*/
@Documented
@Inherited
@ObjectField.AnnotationProcessorClass(NoteRendererClassProcessor.class)
@ObjectType.AnnotationProcessorClass(NoteRendererClassProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.TYPE })
public @interface NoteRendererClass {
Class<? extends NoteRenderer> value();
}
private static class NoteRendererClassProcessor implements
ObjectField.AnnotationProcessor<NoteRendererClass>,
ObjectType.AnnotationProcessor<NoteRendererClass> {
@Override
public void process(ObjectType type, ObjectField field, NoteRendererClass annotation) {
field.as(ToolUi.class).setNoteRendererClass(annotation.value());
}
@Override
public void process(ObjectType type, NoteRendererClass annotation) {
type.as(ToolUi.class).setNoteRendererClass(annotation.value());
}
}
/**
* Specifies the note, in raw HTML, displayed along with the target
* in the UI.
*/
@Documented
@Inherited
@ObjectField.AnnotationProcessorClass(NoteHtmlProcessor.class)
@ObjectType.AnnotationProcessorClass(NoteHtmlProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.TYPE })
public @interface NoteHtml {
String value();
}
private static class NoteHtmlProcessor implements
ObjectField.AnnotationProcessor<NoteHtml>,
ObjectType.AnnotationProcessor<NoteHtml> {
@Override
public void process(ObjectType type, ObjectField field, NoteHtml annotation) {
field.as(ToolUi.class).setNoteHtml(annotation.value());
}
@Override
public void process(ObjectType type, NoteHtml annotation) {
type.as(ToolUi.class).setNoteHtml(annotation.value());
}
}
/**
* Specifies the target field's placeholder text.
*/
@Documented
@ObjectField.AnnotationProcessorClass(PlaceholderProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Placeholder {
/**
* Static placeholder text.
*/
String value() default "";
/**
* Dynamic placeholder text.
*/
String dynamicText() default "";
/**
* {@code true} if the placeholder text should remain and be editable
* when the user clicks into the input.
*/
boolean editable() default false;
}
private static class PlaceholderProcessor implements ObjectField.AnnotationProcessor<Annotation> {
@Override
public void process(ObjectType type, ObjectField field, Annotation annotation) {
ToolUi ui = field.as(ToolUi.class);
if (annotation instanceof FieldPlaceholder) {
ui.setPlaceholder(((FieldPlaceholder) annotation).value());
} else {
Placeholder placeholder = (Placeholder) annotation;
ui.setPlaceholder(placeholder.value());
ui.setPlaceholderDynamicText(placeholder.dynamicText());
ui.setPlaceholderEditable(placeholder.editable());
}
}
}
/** Specifies whether the target is read-only. */
@Documented
@ObjectField.AnnotationProcessorClass(ReadOnlyProcessor.class)
@ObjectType.AnnotationProcessorClass(ReadOnlyProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ReadOnly {
boolean value() default true;
}
private static class ReadOnlyProcessor implements
ObjectField.AnnotationProcessor<ReadOnly>,
ObjectType.AnnotationProcessor<ReadOnly> {
@Override
public void process(ObjectType type, ObjectField field, ReadOnly annotation) {
field.as(ToolUi.class).setReadOnly(annotation.value());
}
@Override
public void process(ObjectType type, ReadOnly annotation) {
type.as(ToolUi.class).setReadOnly(annotation.value());
}
}
/**
* Specifies whether the instances of the target type can be referenced
* by a {@linkplain com.psddev.dari.db.ReferentialText referential text}
* object.
*/
@Documented
@Inherited
@ObjectType.AnnotationProcessorClass(ReferenceableProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Referenceable {
boolean value() default true;
Class<? extends Reference> via() default Reference.class;
}
private static class ReferenceableProcessor implements ObjectType.AnnotationProcessor<Referenceable> {
@Override
public void process(ObjectType type, Referenceable annotation) {
type.as(ToolUi.class).setReferenceable(annotation.value());
type.as(ToolUi.class).setReferenceableViaClass(annotation.via());
}
}
/**
* Specifies whether the target field should offer rich-text editing
* options.
*/
@Documented
@ObjectField.AnnotationProcessorClass(RichTextProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface RichText {
boolean value() default true;
}
private static class RichTextProcessor implements ObjectField.AnnotationProcessor<RichText> {
@Override
public void process(ObjectType type, ObjectField field, RichText annotation) {
field.as(ToolUi.class).setRichText(annotation.value());
}
}
/**
* Specifies whether the target field display should be scrambled.
*/
@Documented
@ObjectField.AnnotationProcessorClass(SecretProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Secret {
boolean value() default true;
}
private static class SecretProcessor implements ObjectField.AnnotationProcessor<Secret> {
@Override
public void process(ObjectType type, ObjectField field, Secret annotation) {
field.as(ToolUi.class).setSecret(annotation.value());
}
}
/**
* Specifies whether the target field should be offered as a sortable
* field in search.
*/
@Documented
@ObjectField.AnnotationProcessorClass(SortableProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sortable {
boolean value() default true;
}
private static class SortableProcessor implements ObjectField.AnnotationProcessor<Sortable> {
@Override
public void process(ObjectType type, ObjectField field, Sortable annotation) {
field.as(ToolUi.class).setSortable(annotation.value());
}
}
/**
* Specifies the standard image sizes that would be applied to the target
* field.
*/
@Documented
@ObjectField.AnnotationProcessorClass(StandardImageSizesProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface StandardImageSizes {
String[] value();
}
private static class StandardImageSizesProcessor implements ObjectField.AnnotationProcessor<StandardImageSizes> {
@Override
public void process(ObjectType type, ObjectField field, StandardImageSizes annotation) {
Collections.addAll(field.as(ToolUi.class).getStandardImageSizes(), annotation.value());
}
}
/** Specifies whether the target field should offer suggestions. */
@Documented
@ObjectField.AnnotationProcessorClass(SuggestionsProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Suggestions {
boolean value() default true;
}
private static class SuggestionsProcessor implements ObjectField.AnnotationProcessor<Suggestions> {
@Override
public void process(ObjectType type, ObjectField field, Suggestions annotation) {
field.as(ToolUi.class).setSuggestions(annotation.value());
}
}
/** Specifies the suggested maximum size of the target field value. */
@Documented
@ObjectField.AnnotationProcessorClass(SuggestedMaximumProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface SuggestedMaximum {
double value();
}
private static class SuggestedMaximumProcessor implements ObjectField.AnnotationProcessor<Annotation> {
@Override
public void process(ObjectType type, ObjectField field, Annotation annotation) {
field.as(ToolUi.class).setSuggestedMaximum(annotation instanceof FieldSuggestedMaximum ?
((FieldSuggestedMaximum) annotation).value() :
((SuggestedMaximum) annotation).value());
}
}
/** Specifies the suggested minimum size of the target field value. */
@Documented
@ObjectField.AnnotationProcessorClass(SuggestedMinimumProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface SuggestedMinimum {
double value();
}
private static class SuggestedMinimumProcessor implements ObjectField.AnnotationProcessor<Annotation> {
@Override
public void process(ObjectType type, ObjectField field, Annotation annotation) {
field.as(ToolUi.class).setSuggestedMinimum(annotation instanceof FieldSuggestedMinimum ?
((FieldSuggestedMinimum) annotation).value() :
((SuggestedMinimum) annotation).value());
}
}
/**
* Specifies the tab that the target field belongs to.
*/
@Documented
@ObjectField.AnnotationProcessorClass(TabProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Tab {
String value();
}
private static class TabProcessor implements ObjectField.AnnotationProcessor<Tab> {
@Override
public void process(ObjectType type, ObjectField field, Tab annotation) {
field.as(ToolUi.class).setTab(annotation.value());
}
}
// --- Legacy ---
private static final String FIELD_PREFIX = "cms.ui.";
private static final String OPTION_PREFIX = "cms.ui.";
// --- Type annotations ---
/**
* Specifies an array of compatible types that the target type may
* switch to.
*/
@Documented
@Inherited
@ObjectType.AnnotationProcessorClass(CompatibleTypesProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CompatibleTypes {
Class<?>[] value();
}
private static class CompatibleTypesProcessor implements ObjectType.AnnotationProcessor<CompatibleTypes> {
@Override
public void process(ObjectType type, CompatibleTypes annotation) {
Set<ObjectType> compatibleTypes = new HashSet<ObjectType>();
DatabaseEnvironment environment = type.getState().getDatabase().getEnvironment();
for (Class<?> typeClass : annotation.value()) {
ObjectType compatibleType = environment.getTypeByClass(typeClass);
if (compatibleType != null) {
compatibleTypes.add(compatibleType);
}
}
setCompatibleTypes(type, compatibleTypes);
}
}
private static final String COMPATIBLE_TYPES_FIELD = FIELD_PREFIX + "compatibleTypes";
/** Returns the set of types that the given {@code type} may switch to. */
public static Set<ObjectType> getCompatibleTypes(ObjectType type) {
Set<ObjectType> types = new HashSet<ObjectType>();
if (type != null) {
Collection<?> ids = (Collection<?>) type.getState().get(COMPATIBLE_TYPES_FIELD);
if (!ObjectUtils.isBlank(ids)) {
DatabaseEnvironment environment = type.getState().getDatabase().getEnvironment();
for (Object idObject : ids) {
ObjectType compatibleType = environment.getTypeById(ObjectUtils.to(UUID.class, idObject));
if (compatibleType != null) {
types.add(compatibleType);
}
}
}
}
return types;
}
/** Sets the set of types that the given {@code type} may switch to. */
public static void setCompatibleTypes(ObjectType type, Iterable<ObjectType> compatibleTypes) {
Set<UUID> compatibleTypeIds;
if (ObjectUtils.isBlank(compatibleTypes)) {
compatibleTypeIds = null;
} else {
compatibleTypeIds = new HashSet<UUID>();
for (ObjectType compatibleType : compatibleTypes) {
if (compatibleType != null) {
compatibleTypeIds.add(compatibleType.getId());
}
}
}
type.getState().put(COMPATIBLE_TYPES_FIELD, compatibleTypeIds);
}
// --- Field annotations ---
/** Specifies the internal type used to render the target field. */
@Documented
@ObjectField.AnnotationProcessorClass(FieldDisplayTypeProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldDisplayType {
String value();
}
private static class FieldDisplayTypeProcessor implements ObjectField.AnnotationProcessor<FieldDisplayType> {
@Override
public void process(ObjectType type, ObjectField field, FieldDisplayType annotation) {
setFieldDisplayType(field, annotation.value());
}
}
private static final String FIELD_INTERNAL_TYPE_OPTION = OPTION_PREFIX + "internalType";
/** Returns the internal type used to render the given {@code field}. */
public static String getFieldDisplayType(ObjectField field) {
return (String) field.getOptions().get(FIELD_INTERNAL_TYPE_OPTION);
}
/** Sets the internal type used to render the given {@code field}. */
public static void setFieldDisplayType(ObjectField field, String type) {
field.getOptions().put(FIELD_INTERNAL_TYPE_OPTION, type);
}
// ---
/**
* Specifies whether the values in the target field should be
* sorted before being saved.
*/
@Documented
@ObjectField.AnnotationProcessorClass(FieldSortedProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldSorted {
boolean value() default true;
}
private static class FieldSortedProcessor implements ObjectField.AnnotationProcessor<FieldSorted> {
@Override
public void process(ObjectType type, ObjectField field, FieldSorted annotation) {
setFieldSorted(field, annotation.value());
}
}
private static final String FIELD_SORTED_OPTION = OPTION_PREFIX + "sorted";
/**
* Returns {@code true} if the values in the given {@code field}
* should be sorted before being saved.
*/
public static boolean isFieldSorted(ObjectField field) {
return Boolean.TRUE.equals(field.getOptions().get(FIELD_SORTED_OPTION));
}
/**
* Sets whether the values in the given {@code field} should be
* sorted before being saved.
*/
public static void setFieldSorted(ObjectField field, boolean isSorted) {
field.getOptions().put(FIELD_SORTED_OPTION, isSorted);
}
// ---
/**
* Specifies whether the target field may only contain objects with
* paths.
*/
@Documented
@ObjectField.AnnotationProcessorClass(OnlyPathedProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface OnlyPathed {
boolean value() default true;
}
private static class OnlyPathedProcessor implements ObjectField.AnnotationProcessor<OnlyPathed> {
@Override
public void process(ObjectType type, ObjectField field, OnlyPathed annotation) {
setOnlyPathed(field, annotation.value());
}
}
private static final String IS_ONLY_PATHED_OPTION = OPTION_PREFIX + "isOnlyPathed";
/**
* Returns {@code true} if the given {@code field} only allows objects
* with paths to be stored.
*/
public static boolean isOnlyPathed(ObjectField field) {
return Boolean.TRUE.equals(field.getOptions().get(IS_ONLY_PATHED_OPTION));
}
/**
* Sets whether the given {@code field} only allows objects with paths
* to be stored.
*/
public static void setOnlyPathed(ObjectField field, boolean isOnlyPathed) {
Map<String, Object> options = field.getOptions();
if (isOnlyPathed) {
options.put(IS_ONLY_PATHED_OPTION, Boolean.TRUE);
} else {
options.remove(IS_ONLY_PATHED_OPTION);
}
}
@ObjectField.AnnotationProcessorClass(StorageSettingProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface StorageSetting {
String value();
}
private static class StorageSettingProcessor implements ObjectField.AnnotationProcessor<StorageSetting> {
@Override
public void process(ObjectType type, ObjectField field, StorageSetting annotation) {
field.as(ToolUi.class).setStorageSetting(annotation.value());
}
}
/**
* Specifies which field should be used as the default sorter.
*/
@ObjectType.AnnotationProcessorClass(DefaultSortFieldProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface DefaultSortField {
String value();
}
private static class DefaultSortFieldProcessor implements ObjectType.AnnotationProcessor<DefaultSortField> {
@Override
public void process(ObjectType type, DefaultSortField annotation) {
type.as(ToolUi.class).setDefaultSortField(annotation.value());
}
}
// --- Deprecated ---
/** @deprecated Use {@link #isHidden()} instead. */
@Deprecated
public static boolean isHidden(ObjectField field) {
return field.as(ToolUi.class).isHidden();
}
/** @deprecated Use {@link #setHidden(boolean)} instead. */
@Deprecated
public static void setHidden(ObjectField field, boolean isHidden) {
field.as(ToolUi.class).setHidden(isHidden);
}
/** @deprecated Use {@link #getNoteHtml} instead. */
@Deprecated
public static String getNote(ObjectField field) {
return ObjectUtils.to(String.class, field.getState().get("cms.ui.note"));
}
/** @deprecated Use {@link #setNoteHtml} instead. */
@Deprecated
public static void setNote(ObjectField field, String note) {
field.as(ToolUi.class).setNoteHtml(StringUtils.escapeHtml(note));
}
/** @deprecated Use {@link Placeholder} instead. */
@Deprecated
@Documented
@ObjectField.AnnotationProcessorClass(PlaceholderProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldPlaceholder {
String value();
}
/** @deprecated Use {@link #getPlaceholder()} instead. */
@Deprecated
public static String getFieldPlaceholder(ObjectField field) {
return field.as(ToolUi.class).getPlaceholder();
}
/** @deprecated Use {@link #setPlaceholder(String)} instead. */
@Deprecated
public static void setFieldPlaceholder(ObjectField field, String placeholder) {
field.as(ToolUi.class).setPlaceholder(placeholder);
}
/** @deprecated Use {@link #isReadOnly()} instead. */
@Deprecated
public static boolean isReadOnly(ObjectField field) {
return field.as(ToolUi.class).isReadOnly();
}
/** @deprecated Use {@link #setReadOnly(boolean)} instead. */
@Deprecated
public static void setReadOnly(ObjectField field, boolean isReadOnly) {
field.as(ToolUi.class).setReadOnly(isReadOnly);
}
/** @deprecated Use {@link #isReferenceable()} instead. */
@Deprecated
public static boolean isReferenceable(ObjectType type) {
return type.as(ToolUi.class).isReferenceable();
}
/** @deprecated Use {@link #setReferenceable(boolean)} instead. */
@Deprecated
public static void setReferenceable(ObjectType type, boolean isReferenceable) {
type.as(ToolUi.class).setReferenceable(isReferenceable);
}
/** @deprecated Use {@link SuggestedMaximum} instead. */
@Deprecated
@Documented
@ObjectField.AnnotationProcessorClass(SuggestedMaximumProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldSuggestedMaximum {
double value();
}
/** @deprecated Use {@link #getSuggestedMaximum()} instead. */
@Deprecated
public static Number getFieldSuggestedMaximum(ObjectField field) {
return field.as(ToolUi.class).getSuggestedMaximum();
}
/** @deprecated Use {@link #setSuggestedMaximum(Number)} instead. */
@Deprecated
public static void setFieldSuggestedMaximum(ObjectField field, Number maximum) {
field.as(ToolUi.class).setSuggestedMaximum(maximum);
}
/** @deprecated Use {@link SuggestedMinimum} instead. */
@Deprecated
@Documented
@ObjectField.AnnotationProcessorClass(SuggestedMinimumProcessor.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldSuggestedMinimum {
double value();
}
/** @deprecated Use {@link #getSuggestedMinimum()} instead. */
@Deprecated
public static Number getFieldSuggestedMinimum(ObjectField field) {
return field.as(ToolUi.class).getSuggestedMinimum();
}
/** @deprecated Use {@link #setSuggestedMinimum(Number)} instead. */
@Deprecated
public static void setFieldSuggestedMinimum(ObjectField field, Number minimum) {
field.as(ToolUi.class).setSuggestedMinimum(minimum);
}
}