AttributeModelImpl.java

package org.dynamoframework.domain.model.impl;

/*-
 * #%L
 * Dynamo Framework
 * %%
 * Copyright (C) 2014 - 2024 Open Circle Solutions
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.dynamoframework.configuration.DynamoProperties;
import org.dynamoframework.domain.model.*;
import org.dynamoframework.domain.model.annotation.SearchMode;
import org.dynamoframework.utils.NumberUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Implementation of the AttributeModel interface - simple container for
 * properties
 *
 * @author bas.rutten
 */
@Data
@EqualsAndHashCode(callSuper = false, of = {"entityModel", "name"})
@ToString
public class AttributeModelImpl implements AttributeModel {

	private final DynamoProperties dynamoProperties;

	private Set<String> allowedExtensions = new HashSet<>();

	private boolean alreadyGrouped;

	private AttributeType attributeType;

	private String autofillInstructions;

	private AttributeBooleanFieldMode booleanFieldMode;

	private final Map<String, String> cascadeAttributes = new HashMap<>();

	private Map<String, CascadeMode> cascadeModes = new HashMap<>();

	private String collectionTableFieldName;

	private String collectionTableName;

	private String currencyCode;

	private Map<String, Object> customSettings = new HashMap<>();

	private AttributeDateType dateType;

	private String defaultDescription;

	private String defaultDisplayFormat;

	private String defaultDisplayName;

	private String defaultFalseRepresentation;

	private String defaultPrompt;

	private String defaultTrueRepresentation;

	private Object defaultValue;

	private Object defaultSearchValue;

	private Object defaultSearchValueFrom;

	private Object defaultSearchValueTo;

	private Map<String, Optional<String>> descriptions = new ConcurrentHashMap<>();

	private Map<String, Optional<String>> displayFormats = new ConcurrentHashMap<>();

	private Map<String, Optional<String>> displayNames = new ConcurrentHashMap<>();

	private boolean downloadAllowed;

	private EditableType editableType;

	private ElementCollectionMode elementCollectionMode;

	private boolean email;

	private AttributeEnumFieldMode enumFieldMode;

	@ToString.Exclude
	private EntityModel<?> entityModel;

	private Map<String, Optional<String>> falseRepresentations = new ConcurrentHashMap<>();

	private String fileNameProperty;

	private Integer gridOrder;

	private final List<String> groupTogetherWith = new ArrayList<>();

	private boolean ignoreInSearchFilter;

	private boolean image;

	private String lookupEntityReference;

	private QueryType lookupQueryType;

	private Integer maxCollectionSize;

	private Integer maxLength;

	private Integer maxLengthInGrid;

	private BigDecimal maxValue;

	private Class<?> memberType;

	private Integer minCollectionSize;

	private Integer minLength;

	private BigDecimal minValue;

	private boolean multipleSearch;

	private String name;

	private boolean neededInData;

	private boolean navigable;

	private String navigationLink;

	private boolean nestedDetails;

	private EntityModel<?> nestedEntityModel;

	private NumberFieldMode numberFieldMode;

	private Integer numberFieldStep;

	private Integer order;

	private boolean percentage;

	private int precision;

	private Map<String, Optional<String>> prompts = new ConcurrentHashMap<>();

	private boolean quickAddAllowed;

	private String replacementSearchPath;

	private String replacementSortPath;

	private boolean required;

	private boolean requiredForSearching;

	private boolean searchCaseSensitive;

	private boolean searchDateOnly;

	private boolean searchForExactValue;

	private SearchMode searchMode;

	private Integer searchOrder;

	private boolean searchPrefixOnly;

	private AttributeSelectMode searchSelectMode;

	private AttributeSelectMode selectMode;

	private boolean showDetailsPaginator;

	private boolean showPassword;

	private boolean sortable;

	private AttributeTextFieldMode textFieldMode;

	private boolean trimSpaces;

	private Map<String, Optional<String>> trueRepresentations = new ConcurrentHashMap<>();

	private Class<?> type;

	private boolean url;

	private boolean visibleInForm;

	private boolean visibleInGrid;

	private boolean setterMethod;

	public AttributeModelImpl(DynamoProperties dynamoProperties) {
		this.dynamoProperties = dynamoProperties;
	}

	@Override
	public void addCascade(final String cascadeTo, final String filterPath, final CascadeMode mode) {
		this.cascadeAttributes.put(cascadeTo, filterPath);
		this.cascadeModes.put(cascadeTo, mode);
	}

	@Override
	public void addGroupTogetherWith(final String path) {
		groupTogetherWith.add(path);
	}

	@Override
	public int compareTo(final AttributeModel other) {
		return this.getOrder() - other.getOrder();
	}

	@Override
	public String getActualSearchPath() {
		return replacementSearchPath != null ? replacementSearchPath : getPath();
	}

	@Override
	public String getActualSortPath() {
		return replacementSortPath != null ? replacementSortPath : getPath();
	}

	@Override
	public Set<String> getCascadeAttributes() {
		return cascadeAttributes.keySet();
	}

	@Override
	public String getCascadeFilterPath(final String cascadeTo) {
		return this.cascadeAttributes.get(cascadeTo);
	}

	@Override
	public CascadeMode getCascadeMode(final String cascadeTo) {
		return this.cascadeModes.get(cascadeTo);
	}

	@Override
	public Object getCustomSetting(String name) {
		return customSettings.get(name);
	}

	@Override
	public String getDescription(Locale locale) {
		// lookup description, falling back to overridden display name or otherwise to
		// the default description
		String displayNameNoDefault = lookupNoDefault(displayNames, locale, EntityModel.DISPLAY_NAME);
		return lookup(descriptions, locale, EntityModel.DESCRIPTION, displayNameNoDefault, defaultDescription);
	}

	@Override
	public String getDisplayFormat(Locale locale) {
		return lookup(displayFormats, locale, EntityModel.DISPLAY_FORMAT, defaultDisplayFormat, null);
	}

	@Override
	public String getDisplayName(Locale locale) {
		return lookup(displayNames, locale, EntityModel.DISPLAY_NAME, defaultDisplayName, null);
	}

	@Override
	public String getFalseRepresentation(Locale locale) {
		return lookup(falseRepresentations, locale, EntityModel.FALSE_REPRESENTATION,
			dynamoProperties.getDefaults().getFalseRepresentations().get(locale.toString()), defaultFalseRepresentation);
	}

	@Override
	public List<String> getGroupTogetherWith() {
		return Collections.unmodifiableList(groupTogetherWith);
	}

	@Override
	public Class<?> getNormalizedType() {
		return getMemberType() != null ? getMemberType() : getType();
	}

	@Override
	public String getPath() {
		String reference = entityModel.getReference();
		int p = reference.indexOf('.');

		if (p <= 0) {
			return name;
		} else {
			return reference.substring(p + 1) + "." + name;
		}
	}

	@Override
	public String getPrompt(Locale locale) {
		if (!dynamoProperties.getDefaults().isUsePromptValue()) {
			return null;
		}

		// look up prompt. If not defined, look up display name
		String displayNameNoDefault = lookupNoDefault(displayNames, locale, EntityModel.DISPLAY_NAME);
		return lookup(prompts, locale, EntityModel.PROMPT, displayNameNoDefault, defaultPrompt);
	}

	@Override
	public String getTrueRepresentation(Locale locale) {
		return lookup(trueRepresentations, locale, EntityModel.TRUE_REPRESENTATION,
			dynamoProperties.getDefaults().getTrueRepresentations().get(locale.toString()), defaultTrueRepresentation);
	}

	@Override
	public boolean isBoolean() {
		return Boolean.class.equals(type) || boolean.class.equals(type);
	}

	@Override
	public boolean isEmbedded() {
		return AttributeType.EMBEDDED.equals(attributeType);
	}

	@Override
	public boolean isNumerical() {
		return NumberUtils.isNumeric(type);
	}

	@Override
	public boolean isIntegral() {
		return NumberUtils.isInteger(type) || NumberUtils.isLong(type);
	}

	public boolean isSearchable() {
		return SearchMode.ADVANCED.equals(searchMode) || SearchMode.ALWAYS.equals(searchMode);
	}

	/**
	 * Looks up the translation of a value for a certain locale
	 *
	 * @param source         the translation cache
	 * @param locale         the locale
	 * @param key            the message key
	 * @param fallBack       the first fallback value
	 * @param secondFallBack the second fallback value
	 * @return the translation for the specified key
	 */
	private String lookup(Map<String, Optional<String>> source, Locale locale, String key, String fallBack,
						  String secondFallBack) {
		if (!source.containsKey(locale.toString())) {
			try {
				ResourceBundle rb = ResourceBundle.getBundle("META-INF/entitymodel", locale);
				String str = rb.getString(getEntityModel().getReference() + "." + getPath() + "." + key);
				source.put(locale.toString(), Optional.of(str));
			} catch (MissingResourceException ex) {
				source.put(locale.toString(), Optional.empty());
			}
		}

		Optional<String> optional = source.get(locale.toString());
		return optional.orElse(fallBack != null ? fallBack : secondFallBack);
	}

	/**
	 * Looks up a key from the message bundle, returning an empty optional if
	 * nothing can be found
	 *
	 * @param source the cache
	 * @param locale the locale
	 * @param key    the message key
	 * @return the value  that the key resolves to
	 */
	private String lookupNoDefault(Map<String, Optional<String>> source, Locale locale, String key) {
		if (!source.containsKey(locale.toString())) {
			try {
				// resource bundle has not been checked yet, check it now
				ResourceBundle rb = ResourceBundle.getBundle("META-INF/entitymodel", locale);
				String str = rb.getString(getEntityModel().getReference() + "." + getPath() + "." + key);
				source.put(locale.toString(), Optional.of(str));
			} catch (MissingResourceException ex) {
				// nothing in resource bundle, store empty value
				source.put(locale.toString(), Optional.empty());
			}
		}
		return source.get(locale.toString()).orElse(null);
	}

	@Override
	public void removeCascades() {
		this.cascadeAttributes.clear();
	}

	@Override
	public void setCustomSetting(String name, Object value) {
		this.customSettings.put(name, value);
	}

	@Override
	public boolean hasSetterMethod() {
		return setterMethod;
	}

	public void setHasSetterMethod(boolean hasSetterMethod) {
		this.setterMethod = hasSetterMethod;
	}
}