Java tutorial
package de.metas.ui.web.view.descriptor; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; import javax.annotation.Nullable; import org.adempiere.exceptions.AdempiereException; import org.slf4j.Logger; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import de.metas.i18n.ITranslatableString; import de.metas.i18n.ImmutableTranslatableString; import de.metas.logging.LogManager; import de.metas.ui.web.cache.ETag; import de.metas.ui.web.cache.ETagAware; import de.metas.ui.web.document.filter.DocumentFilterDescriptor; import de.metas.ui.web.view.IViewRow; import de.metas.ui.web.view.ViewCloseAction; import de.metas.ui.web.view.ViewProfileId; import de.metas.ui.web.view.descriptor.annotation.ViewColumnHelper; import de.metas.ui.web.view.descriptor.annotation.ViewColumnHelper.ClassViewColumnOverrides; import de.metas.ui.web.view.json.JSONViewDataType; import de.metas.ui.web.window.datatypes.WindowId; import de.metas.ui.web.window.descriptor.DetailId; import de.metas.ui.web.window.descriptor.DocumentLayoutElementDescriptor; import de.metas.ui.web.window.descriptor.factory.standard.LayoutFactory; import de.metas.ui.web.window.model.DocumentQueryOrderBy; import de.metas.util.Check; import de.metas.util.GuavaCollectors; import lombok.AccessLevel; import lombok.Getter; import lombok.NonNull; import lombok.RequiredArgsConstructor; /* * #%L * metasfresh-webui-api * %% * Copyright (C) 2017 metas GmbH * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/gpl-2.0.html>. * #L% */ public class ViewLayout implements ETagAware { public static final Builder builder() { return new Builder(); } private static final Logger logger = LogManager.getLogger(ViewLayout.class); private final WindowId windowId; private final DetailId detailId; private final ViewProfileId profileId; private final ITranslatableString caption; private final ITranslatableString description; private final ITranslatableString emptyResultText; private final ITranslatableString emptyResultHint; private final ImmutableList<DocumentFilterDescriptor> filters; private final ImmutableList<DocumentQueryOrderBy> defaultOrderBys; private final ImmutableList<DocumentLayoutElementDescriptor> elements; private final String idFieldName; private final boolean hasAttributesSupport; private final IncludedViewLayout includedViewLayout; private final String allowNewCaption; @Getter private final ImmutableSet<ViewCloseAction> allowedViewCloseActions; private final boolean hasTreeSupport; private final boolean treeCollapsible; private final int treeExpandedDepth; public static final int TreeExpandedDepth_AllCollapsed = 0; public static final int TreeExpandedDepth_ExpandedFirstLevel = 1; public static final int TreeExpandedDepth_AllExpanded = 100; /** If false, frontend shall not allow double clicking on a row in order to open it as a document */ private final boolean allowOpeningRowDetails; // ETag support private static final AtomicInteger nextETagVersionSupplier = new AtomicInteger(1); private final ETag eTag; private ViewLayout(final Builder builder) { windowId = builder.windowId; detailId = builder.getDetailId(); profileId = ViewProfileId.NULL; caption = builder.caption != null ? builder.caption : ImmutableTranslatableString.empty(); description = builder.description != null ? builder.description : ImmutableTranslatableString.empty(); emptyResultText = ImmutableTranslatableString.copyOfNullable(builder.emptyResultText); emptyResultHint = ImmutableTranslatableString.copyOfNullable(builder.emptyResultHint); elements = ImmutableList.copyOf(builder.buildElements()); filters = ImmutableList.copyOf(builder.getFilters()); defaultOrderBys = ImmutableList.copyOf(builder.getDefaultOrderBys()); idFieldName = builder.getIdFieldName(); hasAttributesSupport = builder.hasAttributesSupport; allowedViewCloseActions = builder.getAllowedViewCloseActions(); hasTreeSupport = builder.hasTreeSupport; treeCollapsible = builder.treeCollapsible; treeExpandedDepth = builder.treeExpandedDepth; includedViewLayout = builder.includedViewLayout; allowNewCaption = null; allowOpeningRowDetails = builder.allowOpeningRowDetails; eTag = ETag.of(nextETagVersionSupplier.getAndIncrement(), extractETagAttributes(filters, allowNewCaption)); } /** * copy and override constructor */ private ViewLayout(final ViewLayout from, final WindowId windowId, final ViewProfileId profileId, final ImmutableList<DocumentFilterDescriptor> filters, final ImmutableList<DocumentQueryOrderBy> defaultOrderBys, final String allowNewCaption, final boolean hasTreeSupport, final boolean treeCollapsible, final int treeExpandedDepth, final ImmutableList<DocumentLayoutElementDescriptor> elements) { Check.assumeNotEmpty(elements, "elements is not empty"); this.windowId = windowId; detailId = from.detailId; this.profileId = profileId; caption = from.caption; description = from.description; emptyResultText = from.emptyResultText; emptyResultHint = from.emptyResultHint; this.elements = elements; this.filters = filters; this.defaultOrderBys = defaultOrderBys; idFieldName = from.idFieldName; hasAttributesSupport = from.hasAttributesSupport; allowedViewCloseActions = from.allowedViewCloseActions; this.hasTreeSupport = hasTreeSupport; this.treeCollapsible = treeCollapsible; this.treeExpandedDepth = treeExpandedDepth; includedViewLayout = from.includedViewLayout; this.allowNewCaption = allowNewCaption; this.allowOpeningRowDetails = from.allowOpeningRowDetails; eTag = from.eTag.overridingAttributes(extractETagAttributes(filters, allowNewCaption)); } private static final ImmutableMap<String, String> extractETagAttributes( final ImmutableList<DocumentFilterDescriptor> filters, final String allowNewCaption) { final String filtersNorm = filters == null ? "0" : String.valueOf(filters.hashCode()); final String allowNewCaptionNorm = allowNewCaption == null ? "0" : String.valueOf(allowNewCaption.hashCode()); return ImmutableMap.of("filters", filtersNorm, "allowNew", allowNewCaptionNorm); } @Override public String toString() { return MoreObjects.toStringHelper(this).omitNullValues().add("detailId", detailId).add("caption", caption) .add("elements", elements.isEmpty() ? null : elements).add("eTag", eTag).toString(); } public ChangeBuilder toBuilder() { return new ChangeBuilder(this); } public WindowId getWindowId() { return windowId; } public DetailId getDetailId() { return detailId; } public ViewProfileId getProfileId() { return profileId; } public String getCaption(final String adLanguage) { return caption.translate(adLanguage); } public String getDescription(final String adLanguage) { return description.translate(adLanguage); } public String getEmptyResultText(final String adLanguage) { return emptyResultText.translate(adLanguage); } public String getEmptyResultHint(final String adLanguage) { return emptyResultHint.translate(adLanguage); } public List<DocumentFilterDescriptor> getFilters() { return filters; } public List<DocumentQueryOrderBy> getDefaultOrderBys() { return defaultOrderBys; } public ViewLayout withAllowNewRecordIfPresent(final Optional<String> allowNewCaption) { if (!allowNewCaption.isPresent()) { return this; } return toBuilder().allowNewCaption(allowNewCaption.get()).build(); } public List<DocumentLayoutElementDescriptor> getElements() { return elements; } public boolean hasElements() { return !elements.isEmpty(); } public String getIdFieldName() { return idFieldName; } public boolean isAttributesSupport() { return hasAttributesSupport; } public boolean isTreeSupport() { return hasTreeSupport; } public boolean isTreeCollapsible() { return treeCollapsible; } public int getTreeExpandedDepth() { return treeExpandedDepth; } @Nullable public IncludedViewLayout getIncludedViewLayout() { return includedViewLayout; } public boolean isAllowNew() { return allowNewCaption != null; } public String getAllowNewCaption() { return allowNewCaption; } public boolean isAllowOpeningRowDetails() { return allowOpeningRowDetails; } @Override public ETag getETag() { return eTag; } // // // // // @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public static final class ChangeBuilder { private final ViewLayout from; private WindowId windowId; private ViewProfileId profileId; private Collection<DocumentFilterDescriptor> filters; private String allowNewCaption; private Boolean hasTreeSupport; private Boolean treeCollapsible; private Integer treeExpandedDepth; private ArrayList<DocumentLayoutElementDescriptor> elements = null; private ArrayList<DocumentQueryOrderBy> defaultOrderBys = null; public ViewLayout build() { final WindowId windowIdEffective = getWindowIdEffective(); final ViewProfileId profileIdEffective = !ViewProfileId.isNull(profileId) ? profileId : from.profileId; final ImmutableList<DocumentFilterDescriptor> filtersEffective = ImmutableList .copyOf(filters != null ? filters : from.getFilters()); final String allowNewCaptionEffective = allowNewCaption != null ? allowNewCaption : from.allowNewCaption; final boolean hasTreeSupportEffective = hasTreeSupport != null ? hasTreeSupport.booleanValue() : from.hasTreeSupport; final boolean treeCollapsibleEffective = treeCollapsible != null ? treeCollapsible.booleanValue() : from.treeCollapsible; final int treeExpandedDepthEffective = treeExpandedDepth != null ? treeExpandedDepth.intValue() : from.treeExpandedDepth; final ImmutableList<DocumentLayoutElementDescriptor> elementsEffective = elements != null ? ImmutableList.copyOf(elements) : from.elements; final ImmutableList<DocumentQueryOrderBy> defaultOrderBysEffective = defaultOrderBys != null ? ImmutableList.copyOf(defaultOrderBys) : from.defaultOrderBys; // If there will be no change then return this if (Objects.equals(from.windowId, windowIdEffective) && Objects.equals(from.profileId, profileIdEffective) && Objects.equals(from.filters, filtersEffective) && Objects.equals(from.allowNewCaption, allowNewCaptionEffective) && from.hasTreeSupport == hasTreeSupportEffective && from.treeCollapsible == treeCollapsibleEffective && from.treeExpandedDepth == treeExpandedDepthEffective && Objects.equals(from.elements, elementsEffective) && Objects.equals(from.defaultOrderBys, defaultOrderBysEffective)) { return from; } return new ViewLayout(from, windowIdEffective, profileIdEffective, filtersEffective, defaultOrderBysEffective, allowNewCaptionEffective, hasTreeSupportEffective, treeCollapsibleEffective, treeExpandedDepthEffective, elementsEffective); } public ChangeBuilder windowId(final WindowId windowId) { this.windowId = windowId; return this; } private WindowId getWindowIdEffective() { return windowId != null ? windowId : from.windowId; } public ChangeBuilder profileId(final ViewProfileId profileId) { this.profileId = profileId; return this; } public ChangeBuilder allowNewCaption(final String allowNewCaption) { this.allowNewCaption = allowNewCaption; return this; } public ChangeBuilder filters(final Collection<DocumentFilterDescriptor> filters) { this.filters = filters; return this; } public ChangeBuilder treeSupport(final boolean hasTreeSupport, final Boolean treeCollapsible, final Integer treeExpandedDepth) { this.hasTreeSupport = hasTreeSupport; this.treeCollapsible = treeCollapsible; this.treeExpandedDepth = treeExpandedDepth; return this; } private ArrayList<DocumentLayoutElementDescriptor> getElementsToEdit() { if (elements == null) { elements = new ArrayList<>(from.elements); } return elements; } private void setElements(final ArrayList<DocumentLayoutElementDescriptor> elements) { this.elements = elements; } public ChangeBuilder element(@NonNull final DocumentLayoutElementDescriptor element) { getElementsToEdit().add(element); return this; } public ChangeBuilder elementsOrder(final String... fieldNames) { final ImmutableMap<String, DocumentLayoutElementDescriptor> elementsByFieldName = Maps .uniqueIndex(getElementsToEdit(), DocumentLayoutElementDescriptor::getFirstFieldName); final ArrayList<DocumentLayoutElementDescriptor> elementsNew = new ArrayList<>(); for (final String fieldName : fieldNames) { final DocumentLayoutElementDescriptor element = elementsByFieldName.get(fieldName); if (element == null) { logger.warn( "Field {} was not found. Will be ignored." + "\n Available field names are: {}." + "\n If this is a standard view, pls check if the field added to window {}.", fieldName, elementsByFieldName.keySet(), getWindowIdEffective()); continue; } elementsNew.add(element); } setElements(elementsNew); return this; } private ArrayList<DocumentQueryOrderBy> getDefaultOrderBysToEdit() { if (defaultOrderBys == null) { defaultOrderBys = new ArrayList<>(from.defaultOrderBys); } return defaultOrderBys; } public ChangeBuilder clearDefaultOrderBys() { getDefaultOrderBysToEdit().clear(); return this; } public ChangeBuilder defaultOrderBy(@NonNull final DocumentQueryOrderBy orderBy) { getDefaultOrderBysToEdit().add(orderBy); return this; } } public static final class Builder { private WindowId windowId; private DetailId detailId; private ITranslatableString caption; private ITranslatableString description; private ITranslatableString emptyResultText = LayoutFactory.HARDCODED_TAB_EMPTY_RESULT_TEXT; private ITranslatableString emptyResultHint = LayoutFactory.HARDCODED_TAB_EMPTY_RESULT_HINT; private Collection<DocumentFilterDescriptor> filters = null; private List<DocumentQueryOrderBy> defaultOrderBys = null; private boolean hasAttributesSupport = false; private IncludedViewLayout includedViewLayout; private LinkedHashSet<ViewCloseAction> allowedViewCloseActions; private static final ImmutableSet<ViewCloseAction> DEFAULT_allowedViewCloseActions = ImmutableSet .of(ViewCloseAction.DONE); private boolean hasTreeSupport = false; private boolean treeCollapsible = false; private int treeExpandedDepth = TreeExpandedDepth_AllExpanded; private boolean allowOpeningRowDetails = true; private final List<DocumentLayoutElementDescriptor.Builder> elementBuilders = new ArrayList<>(); private String idFieldName; private Builder() { } public ViewLayout build() { return new ViewLayout(this); } private List<DocumentLayoutElementDescriptor> buildElements() { return elementBuilders.stream().filter(elementBuilder -> elementBuilder.getFieldsCount() > 0) // have some field builders .map(elementBuilder -> elementBuilder.build()).collect(GuavaCollectors.toImmutableList()); } @Override public String toString() { return MoreObjects.toStringHelper(this).add("detailId", detailId).add("caption", caption) .add("elements-count", elementBuilders.size()).toString(); } public Builder setWindowId(final WindowId windowId) { this.windowId = windowId; return this; } public Builder setDetailId(final DetailId detailId) { this.detailId = detailId; return this; } public DetailId getDetailId() { return detailId; } public Builder setCaption(@Nullable final ITranslatableString caption) { this.caption = caption; return this; } public Builder setCaption(@Nullable final String caption) { setCaption(ImmutableTranslatableString.constant(caption)); return this; } public Builder setDescription(@Nullable final ITranslatableString description) { this.description = description; return this; } public Builder setEmptyResultText(final ITranslatableString emptyResultText) { this.emptyResultText = emptyResultText; return this; } public Builder setEmptyResultHint(final ITranslatableString emptyResultHint) { this.emptyResultHint = emptyResultHint; return this; } public Builder clearElements() { elementBuilders.clear(); return this; } public Builder removeElementByFieldName(final String fieldName) { for (final Iterator<DocumentLayoutElementDescriptor.Builder> it = elementBuilders.iterator(); it .hasNext();) { final DocumentLayoutElementDescriptor.Builder element = it.next(); if (element.getFieldNames().contains(fieldName)) { element.removeFieldByFieldName(fieldName); if (element.getFieldsCount() == 0) { it.remove(); continue; } } } return this; } public Builder addElement(@NonNull final DocumentLayoutElementDescriptor.Builder elementBuilder) { elementBuilders.add(elementBuilder); return this; } public Builder addElements( @NonNull final Collection<DocumentLayoutElementDescriptor.Builder> elementBuilders) { elementBuilders.forEach(this::addElement); return this; } public Builder addElements(@NonNull final Stream<DocumentLayoutElementDescriptor.Builder> elementBuilders) { elementBuilders.forEach(this::addElement); return this; } public <T extends IViewRow> Builder addElementsFromViewRowClass(final Class<T> viewRowClass, final JSONViewDataType viewType) { final List<DocumentLayoutElementDescriptor.Builder> elements = ViewColumnHelper .createLayoutElementsForClass(viewRowClass, viewType); if (elements.isEmpty()) { new AdempiereException( "No elements found for viewRowClass=" + viewRowClass + " and viewType=" + viewType) .throwIfDeveloperModeOrLogWarningElse(logger); } addElements(elements); return this; } public <T extends IViewRow> Builder addElementsFromViewRowClassAndFieldNames(final Class<T> viewRowClass, final JSONViewDataType viewDataType, final ClassViewColumnOverrides... columns) { final List<DocumentLayoutElementDescriptor.Builder> elements = ViewColumnHelper .createLayoutElementsForClassAndFieldNames(viewRowClass, viewDataType, columns); Check.assumeNotEmpty(elements, "elements is not empty"); // shall never happen addElements(elements); return this; } public boolean hasElements() { return !elementBuilders.isEmpty(); } public List<DocumentLayoutElementDescriptor.Builder> getElements() { return elementBuilders; } private Collection<DocumentFilterDescriptor> getFilters() { if (filters == null || filters.isEmpty()) { return ImmutableList.of(); } return filters; } public Builder setFilters(final Collection<DocumentFilterDescriptor> filters) { this.filters = filters; return this; } public Builder addFilter(@NonNull final DocumentFilterDescriptor filter) { if (filters == null) { filters = new ArrayList<>(); } filters.add(filter); return this; } private List<DocumentQueryOrderBy> getDefaultOrderBys() { return defaultOrderBys != null ? defaultOrderBys : ImmutableList.of(); } public Builder setDefaultOrderBys(final List<DocumentQueryOrderBy> defaultOrderBys) { this.defaultOrderBys = defaultOrderBys; return this; } public Set<String> getFieldNames() { return elementBuilders.stream().flatMap(element -> element.getFieldNames().stream()) .collect(GuavaCollectors.toImmutableSet()); } public Builder setIdFieldName(final String idFieldName) { this.idFieldName = idFieldName; return this; } private String getIdFieldName() { return idFieldName; } public Builder setHasAttributesSupport(final boolean hasAttributesSupport) { this.hasAttributesSupport = hasAttributesSupport; return this; } public Builder setIncludedViewLayout(final IncludedViewLayout includedViewLayout) { this.includedViewLayout = includedViewLayout; return this; } public Builder clearViewCloseActions() { allowedViewCloseActions = new LinkedHashSet<>(); return this; } public Builder allowViewCloseAction(@NonNull final ViewCloseAction viewCloseAction) { if (allowedViewCloseActions == null) { allowedViewCloseActions = new LinkedHashSet<>(); } allowedViewCloseActions.add(viewCloseAction); return this; } private ImmutableSet<ViewCloseAction> getAllowedViewCloseActions() { return allowedViewCloseActions != null ? ImmutableSet.copyOf(allowedViewCloseActions) : DEFAULT_allowedViewCloseActions; } public Builder setHasTreeSupport(final boolean hasTreeSupport) { this.hasTreeSupport = hasTreeSupport; return this; } public Builder setTreeCollapsible(final boolean treeCollapsible) { this.treeCollapsible = treeCollapsible; return this; } public Builder setTreeExpandedDepth(final int treeExpandedDepth) { this.treeExpandedDepth = treeExpandedDepth; return this; } public Builder setAllowOpeningRowDetails(boolean allowOpeningRowDetails) { this.allowOpeningRowDetails = allowOpeningRowDetails; return this; } } }