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 java.util.stream.Collectors; import com.psddev.cms.rte.DefaultRichTextToolbar; import com.psddev.cms.rte.RichTextToolbar; import com.psddev.dari.db.Database; import com.psddev.dari.db.DatabaseEnvironment; import com.psddev.dari.db.Modification; import com.psddev.dari.db.ObjectField; import com.psddev.dari.db.ObjectMethod; import com.psddev.dari.db.ObjectType; import com.psddev.dari.db.Reference; import com.psddev.dari.util.ObjectUtils; import com.psddev.dari.util.SmsProvider; 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 collectionItemProgress; private Boolean collectionItemToggle; private Boolean collectionItemWeight; private Boolean collectionItemWeightCalculated; private Boolean collectionItemWeightColor; private Boolean collectionItemWeightMarker; private Boolean colorPicker; private String cssClass; private Boolean defaultSearchResult; private Set<String> displayAfter; private Set<String> displayBefore; private boolean displayFirst; private boolean displayGlobalFilters; private Boolean displayGrid; private boolean displayLast; private boolean dropDown; private boolean dropDownSortDescending; private String dropDownSortField; private Boolean expanded; private List<String> fieldDisplayOrder; 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 dynamicInputSearcherPath; private String storagePreviewProcessorApplication; private String storagePreviewProcessorPath; private String languageTag; private ToolUiLayoutElement layoutField; private List<ToolUiLayoutElement> layoutPlaceholders; private Boolean main; private String noteHtml; private String noteRendererClassName; private String placeholder; private Boolean placeholderClearOnChange; private String placeholderDynamicText; private Boolean placeholderEditable; private Boolean publishable; private String publishButtonText; private Boolean referenceable; private String referenceableViaClassName; private Boolean readOnly; private boolean richText; private boolean richTextInline; private String richTextToolbarClassName; private String richTextElementTagName; private Set<String> richTextElementClassNames; 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; private Boolean unlabeled; private Boolean testSms; 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 isCollectionItemProgress() { return Boolean.TRUE.equals(collectionItemProgress); } public void setCollectionItemProgress(boolean collectionItemProgress) { this.collectionItemProgress = collectionItemProgress ? Boolean.TRUE : null; } public boolean isCollectionItemToggle() { return Boolean.TRUE.equals(collectionItemToggle); } public void setCollectionItemToggle(boolean collectionItemToggle) { this.collectionItemToggle = collectionItemToggle ? Boolean.TRUE : null; } public boolean isCollectionItemWeight() { return Boolean.TRUE.equals(collectionItemWeight); } public void setCollectionItemWeight(boolean collectionItemWeight) { this.collectionItemWeight = collectionItemWeight ? Boolean.TRUE : null; } public boolean isCollectionItemWeightCalculated() { return Boolean.TRUE.equals(collectionItemWeightCalculated); } public void setCollectionItemWeightCalculated(boolean collectionItemWeightCalculated) { this.collectionItemWeightCalculated = collectionItemWeightCalculated ? Boolean.TRUE : null; } public boolean isCollectionItemWeightColor() { return Boolean.TRUE.equals(collectionItemWeightColor); } public void setCollectionItemWeightColor(boolean collectionItemWeightColor) { this.collectionItemWeightColor = collectionItemWeightColor ? Boolean.TRUE : null; } public boolean isCollectionItemWeightMarker() { return Boolean.TRUE.equals(collectionItemWeightMarker); } public void setCollectionItemWeightMarker(boolean collectionItemWeightMarker) { this.collectionItemWeightMarker = collectionItemWeightMarker ? Boolean.TRUE : null; } 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 getDefaultSearchResult() { return defaultSearchResult; } public void setDefaultSearchResult(Boolean defaultSearchResult) { this.defaultSearchResult = defaultSearchResult; } public Set<String> getDisplayAfter() { if (displayAfter == null) { displayAfter = new LinkedHashSet<>(); } return displayAfter; } public void setDisplayAfter(Set<String> displayAfter) { this.displayAfter = displayAfter; } public Set<String> getDisplayBefore() { if (displayBefore == null) { displayBefore = new LinkedHashSet<>(); } return displayBefore; } public void setDisplayBefore(Set<String> displayBefore) { this.displayBefore = displayBefore; } public boolean isDisplayFirst() { return displayFirst; } public void setDisplayFirst(boolean displayFirst) { this.displayFirst = displayFirst; } public boolean isDisplayGlobalFilters() { return displayGlobalFilters; } public void setDisplayGlobalFilters(boolean displayGlobalFilters) { this.displayGlobalFilters = displayGlobalFilters; } public Boolean getDisplayGrid() { return displayGrid; } public void setDisplayGrid(boolean displayGrid) { this.displayGrid = displayGrid; } public boolean isDisplayGrid() { Boolean displayGrid = getDisplayGrid(); if (displayGrid != null) { return displayGrid; } Object object = getOriginalObject(); if (!(object instanceof ObjectField)) { return false; } ObjectField field = (ObjectField) object; final List<ObjectType> validTypes = field.as(ToolUi.class).findDisplayTypes(); if (isValueExternal(field)) { // all display types must be previewable for (ObjectType displayType : field.as(ToolUi.class).findDisplayTypes()) { if (ObjectUtils.isBlank(displayType.getPreviewField())) { return false; } } return true; } else { // always show grid view by default for types with bulkUpload capability Set<ObjectType> bulkUploadTypes = new HashSet<ObjectType>(); for (ObjectType t : validTypes) { for (ObjectField f : t.getFields()) { if (f.as(ToolUi.class).isBulkUpload()) { for (ObjectType ft : f.getTypes()) { bulkUploadTypes.add(ft); } } } } return !bulkUploadTypes.isEmpty(); } } 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 isDropDownSortDescending() { return dropDownSortDescending; } public void setDropDownSortDescending(boolean dropDownSortDescending) { this.dropDownSortDescending = dropDownSortDescending; } public String getDropDownSortField() { return dropDownSortField; } public void setDropDownSortField(String dropDownSortField) { this.dropDownSortField = dropDownSortField; } public boolean isExpanded() { return Boolean.TRUE.equals(expanded); } public void setExpanded(boolean expanded) { this.expanded = expanded ? Boolean.TRUE : null; } public List<String> getFieldDisplayOrder() { if (fieldDisplayOrder == null) { fieldDisplayOrder = new ArrayList<>(); } return fieldDisplayOrder; } public void setFieldDisplayOrder(List<String> fieldDisplayOrder) { this.fieldDisplayOrder = fieldDisplayOrder; } 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 Boolean getHidden() { return hidden; } 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 getDynamicInputSearcherPath() { return dynamicInputSearcherPath; } public void setDynamicInputSearcherPath(String dynamicInputSearcherPath) { this.dynamicInputSearcherPath = dynamicInputSearcherPath; } 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 getLanguageTag() { return languageTag; } public void setLanguageTag(String languageTag) { this.languageTag = languageTag; } public ToolUiLayoutElement getLayoutField() { return layoutField; } public void setLayoutField(ToolUiLayoutElement layoutField) { this.layoutField = layoutField; } public List<ToolUiLayoutElement> getLayoutPlaceholders() { if (layoutPlaceholders == null) { layoutPlaceholders = new ArrayList<ToolUiLayoutElement>(); } return layoutPlaceholders; } public void setLayoutPlaceholders(List<ToolUiLayoutElement> layoutPlaceholders) { this.layoutPlaceholders = layoutPlaceholders; } 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 isPublishable() { return Boolean.TRUE.equals(publishable); } public void setPublishable(boolean publishable) { this.publishable = publishable ? Boolean.TRUE : null; } public String getPublishButtonText() { return publishButtonText; } public void setPublishButtonText(String publishButtonText) { this.publishButtonText = publishButtonText; } public boolean isReadOnly() { if (readOnly == null) { readOnly = ObjectUtils.to(Boolean.class, getState().get("cms.ui.isReadOnly")); } if (getOriginalObject() instanceof ObjectMethod) { return true; } return readOnly != null ? readOnly : false; } public void setReadOnly(boolean readOnly) { this.readOnly = readOnly; } public boolean isPlaceholderClearOnChange() { return Boolean.TRUE.equals(placeholderClearOnChange); } public void setPlaceholderClearOnChange(boolean placeholderClearOnChange) { this.placeholderClearOnChange = Boolean.TRUE.equals(placeholderClearOnChange) ? Boolean.TRUE : null; } 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 isRichTextInline() { return richTextInline; } public void setRichTextInline(boolean richTextInline) { this.richTextInline = richTextInline; } public String getRichTextToolbarClassName() { return richTextToolbarClassName; } public void setRichTextToolbarClassName(String richTextToolbarClassName) { this.richTextToolbarClassName = richTextToolbarClassName; } public String getRichTextElementTagName() { return richTextElementTagName; } public void setRichTextElementTagName(String richTextElementTagName) { this.richTextElementTagName = richTextElementTagName; } public Set<String> getRichTextElementClassNames() { if (richTextElementClassNames == null) { richTextElementClassNames = new LinkedHashSet<>(); } return richTextElementClassNames; } public void setRichTextElementClassNames(Set<String> richTextElementClassNames) { this.richTextElementClassNames = richTextElementClassNames; } public Set<String> findRichTextElementTags() { Set<String> classNames = getRichTextElementClassNames(); return Database.Static.getDefault().getEnvironment().getTypes().stream() .filter(t -> !Collections.disjoint(t.getGroups(), classNames)) .map(t -> t.as(ToolUi.class).getRichTextElementTagName()) .filter(n -> !ObjectUtils.isBlank(n)) .collect(Collectors.toSet()); } 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() { Object object = getOriginalObject(); if (object instanceof ObjectField && !ObjectUtils.isBlank(((ObjectField) object).getPredicate())) { return false; } 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; } public boolean isMain() { return Boolean.TRUE.equals(main); } public void setMain(boolean main) { this.main = main; } public boolean isUnlabeled() { return Boolean.TRUE.equals(unlabeled); } public void setUnlabeled(boolean unlabeled) { this.unlabeled = unlabeled ? Boolean.TRUE : null; } public boolean isTestSms() { return Boolean.TRUE.equals(testSms); } public void setTestSms(boolean testSms) { this.testSms = testSms ? Boolean.TRUE : null; } /** * @return {@code true} if default {@link SmsProvider} exists and * {@link TestSms} is singular text UI annotation, {@code false} otherwise. */ public boolean isEffectivelyTestSms() { try { SmsProvider.Static.getDefault(); } catch (IllegalStateException error) { return false; } return isTestSms() && !isColorPicker() && !isSecret(); } /** * 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.as(ToolUi.class).isHidden()) { if (t.getObjectClassName() == null) { displayTypes.add(t); } else if (t.getObjectClass() != null) { displayTypes.add(t); } } } } return displayTypes; } public static boolean isValueExternal(ObjectField field) { final List<ObjectType> validTypes = field.as(ToolUi.class).findDisplayTypes(); boolean isValueExternal = !field.isEmbedded(); if (isValueExternal && validTypes != null && validTypes.size() > 0) { for (ObjectType type : validTypes) { if (!type.isEmbedded()) { // value is external if any of the valid types must be externally referenced. return true; } } return false; } return isValueExternal; } /** * 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, ElementType.METHOD }) 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 target field should be displayed using the progress bar and label. * Expected field values are between 0.0 and 1.0. */ @Documented @ObjectField.AnnotationProcessorClass(CollectionItemProgressProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface CollectionItemProgress { boolean value() default true; } private static class CollectionItemProgressProcessor implements ObjectField.AnnotationProcessor<CollectionItemProgress> { @Override public void process(ObjectType type, ObjectField field, CollectionItemProgress annotation) { field.as(ToolUi.class).setCollectionItemProgress(annotation.value()); } } /** * Specifies whether the target field should be displayed using a toggle on the collection item. */ @Documented @ObjectField.AnnotationProcessorClass(CollectionItemToggleProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface CollectionItemToggle { boolean value() default true; } private static class CollectionItemToggleProcessor implements ObjectField.AnnotationProcessor<CollectionItemToggle> { @Override public void process(ObjectType type, ObjectField field, CollectionItemToggle annotation) { field.as(ToolUi.class).setCollectionItemToggle(annotation.value()); } } /** * Specifies whether the target field should be displayed using the weighted collection UI. * Expected field values are between 0.0 and 1.0. Fields that are {@code calculated} will * not allow weights to be edited through the default UI. * */ @Documented @ObjectField.AnnotationProcessorClass(CollectionItemWeightProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface CollectionItemWeight { boolean value() default true; boolean calculated() default false; } private static class CollectionItemWeightProcessor implements ObjectField.AnnotationProcessor<CollectionItemWeight> { @Override public void process(ObjectType type, ObjectField field, CollectionItemWeight annotation) { field.as(ToolUi.class).setCollectionItemWeight(annotation.value()); field.as(ToolUi.class).setCollectionItemWeightCalculated(annotation.calculated()); } } /** * Specifies a field to be used as color in the UI produced by repeatable objects with * a {@link CollectionItemWeight} annotation. The field should be a {@link String} with * a value of hex code of color. */ @Documented @ObjectField.AnnotationProcessorClass(CollectionItemWeightColorProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface CollectionItemWeightColor { boolean value() default true; } private static class CollectionItemWeightColorProcessor implements ObjectField.AnnotationProcessor<CollectionItemWeightColor> { @Override public void process(ObjectType type, ObjectField field, CollectionItemWeightColor annotation) { field.as(ToolUi.class).setCollectionItemWeightColor(annotation.value()); } } /** * Specifies a field to be used as markers in the UI produced by repeatable objects with * a {@link CollectionItemWeight} annotation. The field may be a {@link Collection} or single * field value of a {@code double} with expected value(s) between 0.0 and 1.0. */ @Documented @ObjectField.AnnotationProcessorClass(CollectionItemWeightMarkerProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface CollectionItemWeightMarker { boolean value() default true; } private static class CollectionItemWeightMarkerProcessor implements ObjectField.AnnotationProcessor<CollectionItemWeightMarker> { @Override public void process(ObjectType type, ObjectField field, CollectionItemWeightMarker annotation) { field.as(ToolUi.class).setCollectionItemWeightMarker(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 the HTML element that represents the * target type or field. * * <p>If the annotation is on a type, the CSS class is added to the * {@code .objectInputs} element.</p> * * <p>If the annotation is on a field, the CSS class is added to the * {@code .inputContainer} element.</p> */ @Documented @ObjectField.AnnotationProcessorClass(CssClassProcessor.class) @ObjectType.AnnotationProcessorClass(CssClassProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) public @interface CssClass { String value(); } private static class CssClassProcessor implements ObjectField.AnnotationProcessor<CssClass>, ObjectType.AnnotationProcessor<CssClass> { @Override public void process(ObjectType type, ObjectField field, CssClass annotation) { field.as(ToolUi.class).setCssClass(annotation.value()); } @Override public void process(ObjectType type, CssClass annotation) { type.as(ToolUi.class).setCssClass(annotation.value()); } } /** * Specifies that the target field should display in the search result * by default. */ @Documented @ObjectField.AnnotationProcessorClass(DefaultSearchResultProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.METHOD }) public @interface DefaultSearchResult { boolean value() default true; } private static class DefaultSearchResultProcessor implements ObjectField.AnnotationProcessor<DefaultSearchResult> { @Override public void process(ObjectType type, ObjectField field, DefaultSearchResult annotation) { field.as(ToolUi.class).setDefaultSearchResult(annotation.value()); } } @Documented @ObjectField.AnnotationProcessorClass(DisplayAfterProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.METHOD }) public @interface DisplayAfter { String[] value(); } private static class DisplayAfterProcessor implements ObjectField.AnnotationProcessor<DisplayAfter> { @Override public void process(ObjectType type, ObjectField field, DisplayAfter annotation) { Collections.addAll(field.as(ToolUi.class).getDisplayAfter(), annotation.value()); } } @Documented @ObjectField.AnnotationProcessorClass(DisplayBeforeProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.METHOD }) public @interface DisplayBefore { String[] value(); } private static class DisplayBeforeProcessor implements ObjectField.AnnotationProcessor<DisplayBefore> { @Override public void process(ObjectType type, ObjectField field, DisplayBefore annotation) { Collections.addAll(field.as(ToolUi.class).getDisplayBefore(), 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, ElementType.METHOD }) 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 type displays global search filters. */ @Documented @Inherited @ObjectType.AnnotationProcessorClass(DisplayGlobalFiltersProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DisplayGlobalFilters { boolean value() default true; } private static class DisplayGlobalFiltersProcessor implements ObjectType.AnnotationProcessor<DisplayGlobalFilters> { @Override public void process(ObjectType type, DisplayGlobalFilters annotation) { type.as(ToolUi.class).setDisplayGlobalFilters(annotation.value()); } } /** * Specifies that the target field should be displayed in a grid layout. */ @Documented @ObjectField.AnnotationProcessorClass(DisplayGridProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.METHOD }) public @interface DisplayGrid { boolean value() default true; } private static class DisplayGridProcessor implements ObjectField.AnnotationProcessor<DisplayGrid> { @Override public void process(ObjectType type, ObjectField field, DisplayGrid annotation) { field.as(ToolUi.class).setDisplayGrid(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, ElementType.METHOD }) 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()); } } @Documented @Inherited @ObjectType.AnnotationProcessorClass(FieldDisplayOrderProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface FieldDisplayOrder { String[] value(); } private static class FieldDisplayOrderProcessor implements ObjectType.AnnotationProcessor<FieldDisplayOrder> { @Override public void process(ObjectType type, FieldDisplayOrder annotation) { Collections.addAll(type.as(ToolUi.class).getFieldDisplayOrder(), 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, ElementType.METHOD }) public @interface DropDown { boolean value() default true; String sortField() default ""; boolean sortDescending() default false; } 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()); field.as(ToolUi.class).setDropDownSortField(StringUtils.isBlank(annotation.sortField()) ? null : annotation.sortField()); field.as(ToolUi.class).setDropDownSortDescending(annotation.sortDescending()); } } /** * 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, ElementType.METHOD }) 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, ElementType.METHOD }) 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, ElementType.METHOD }) 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, ElementType.METHOD }) 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, ElementType.METHOD }) 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() default ""; String dynamicPath() default ""; } private static class InputSearcherPathProcessor implements ObjectField.AnnotationProcessor<InputSearcherPath> { @Override public void process(ObjectType type, ObjectField field, InputSearcherPath annotation) { ToolUi fieldMod = field.as(ToolUi.class); fieldMod.setInputSearcherPath(annotation.value()); fieldMod.setDynamicInputSearcherPath(annotation.dynamicPath()); } } /** * Specifies the language of the text in the target type or field. */ @Documented @Inherited @ObjectField.AnnotationProcessorClass(LanguageTagProcessor.class) @ObjectType.AnnotationProcessorClass(LanguageTagProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.TYPE }) public @interface LanguageTag { public String value(); } private static class LanguageTagProcessor implements ObjectField.AnnotationProcessor<LanguageTag>, ObjectType.AnnotationProcessor<LanguageTag> { @Override public void process(ObjectType type, ObjectField field, LanguageTag annotation) { field.as(ToolUi.class).setLanguageTag(annotation.value()); } @Override public void process(ObjectType type, LanguageTag annotation) { type.as(ToolUi.class).setLanguageTag(annotation.value()); } } @Documented @ObjectField.AnnotationProcessorClass(LayoutFieldProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface LayoutField { public int left(); public int top(); public int width(); public int height(); public String dynamicText() default ""; } private static class LayoutFieldProcessor implements ObjectField.AnnotationProcessor<LayoutField> { @Override public void process(ObjectType type, ObjectField field, LayoutField annotation) { ToolUiLayoutElement element = new ToolUiLayoutElement(); element.setLeft(annotation.left()); element.setTop(annotation.top()); element.setWidth(annotation.width()); element.setHeight(annotation.height()); element.setDynamicText(annotation.dynamicText()); field.as(ToolUi.class).setLayoutField(element); } } @Documented @Retention(RetentionPolicy.RUNTIME) public @interface LayoutPlaceholder { public String name(); public int left(); public int top(); public int width(); public int height(); public String dynamicText() default ""; } @Documented @Inherited @ObjectType.AnnotationProcessorClass(LayoutPlaceholdersProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface LayoutPlaceholders { public LayoutPlaceholder[] value(); } private static class LayoutPlaceholdersProcessor implements ObjectType.AnnotationProcessor<LayoutPlaceholders> { @Override public void process(ObjectType type, LayoutPlaceholders annotation) { for (LayoutPlaceholder placeholder : annotation.value()) { ToolUiLayoutElement element = new ToolUiLayoutElement(); element.setName(placeholder.name()); element.setLeft(placeholder.left()); element.setTop(placeholder.top()); element.setWidth(placeholder.width()); element.setHeight(placeholder.height()); element.setDynamicText(placeholder.dynamicText()); type.as(ToolUi.class).getLayoutPlaceholders().add(element); } } } /** * Specifies whether the class will be listed as a main content type. */ @Documented @Inherited @ObjectType.AnnotationProcessorClass(MainProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Main { boolean value() default true; } private static class MainProcessor implements ObjectType.AnnotationProcessor<Main> { @Override public void process(ObjectType objectType, Main annotation) { objectType.as(ToolUi.class).setMain(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, ElementType.METHOD }) 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, ElementType.METHOD }) 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, ElementType.METHOD }) 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, ElementType.METHOD }) public @interface Placeholder { /** * Static placeholder text. */ String value() default ""; /** * {@code true} if the target field should be cleared when the * placeholder text changes. */ boolean clearOnChange() default false; /** * 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.setPlaceholderClearOnChange(placeholder.clearOnChange()); ui.setPlaceholderDynamicText(placeholder.dynamicText()); ui.setPlaceholderEditable(placeholder.editable()); } } } @Documented @Inherited @ObjectType.AnnotationProcessorClass(PublishableProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Publishable { boolean value() default true; } private static class PublishableProcessor implements ObjectType.AnnotationProcessor<Publishable> { @Override public void process(ObjectType type, Publishable annotation) { type.as(ToolUi.class).setPublishable(annotation.value()); } } /** * Specifies the note displayed along with the target in the UI. */ @Documented @Inherited @ObjectType.AnnotationProcessorClass(PublishButtonTextProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface PublishButtonText { String value(); } private static class PublishButtonTextProcessor implements ObjectType.AnnotationProcessor<PublishButtonText> { @Override public void process(ObjectType objectType, PublishButtonText annotation) { objectType.as(ToolUi.class).setPublishButtonText(annotation.value()); } } /** Specifies whether the target is read-only. */ @Documented @Inherited @ObjectField.AnnotationProcessorClass(ReadOnlyProcessor.class) @ObjectType.AnnotationProcessorClass(ReadOnlyProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.TYPE }) 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. Optionally enable block elements by setting {@code inline} * to {@code} false. */ @Documented @ObjectField.AnnotationProcessorClass(RichTextProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.METHOD }) public @interface RichText { boolean value() default true; boolean inline() default true; Class<? extends RichTextToolbar> toolbar() default DefaultRichTextToolbar.class; } private static class RichTextProcessor implements ObjectField.AnnotationProcessor<RichText> { @Override public void process(ObjectType type, ObjectField field, RichText annotation) { ToolUi ui = field.as(ToolUi.class); ui.setRichText(annotation.value()); ui.setRichTextInline(annotation.inline()); ui.setRichTextToolbarClassName(annotation.toolbar().getName()); } } /** * Specifies whether the target field display should be scrambled. */ @Documented @ObjectField.AnnotationProcessorClass(SecretProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.METHOD }) 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, ElementType.METHOD }) 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, ElementType.METHOD }) 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()); } } /** * Specifies whether the field should be labeled or not. */ @Documented @ObjectField.AnnotationProcessorClass(UnlabeledProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.METHOD }) public @interface Unlabeled { boolean value() default true; } private static class UnlabeledProcessor implements ObjectField.AnnotationProcessor<Unlabeled> { @Override public void process(ObjectType type, ObjectField field, Unlabeled annotation) { field.as(ToolUi.class).setUnlabeled(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, ElementType.METHOD }) 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()); } } /** * Specifies whether the target field should display the test sms option. */ @Documented @ObjectField.AnnotationProcessorClass(TestSmsProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @interface TestSms { boolean value() default true; } private static class TestSmsProcessor implements ObjectField.AnnotationProcessor<TestSms> { @Override public void process(ObjectType type, ObjectField field, TestSms annotation) { field.as(ToolUi.class).setTestSms(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); } }