001/*
002 *  Copyright 2023 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.cms.search.cocoon;
017
018import java.util.List;
019import java.util.Optional;
020
021import org.apache.commons.lang3.StringUtils;
022import org.apache.commons.lang3.Strings;
023
024import org.ametys.runtime.model.ModelItem;
025import org.ametys.runtime.model.ModelViewItem;
026import org.ametys.runtime.model.SimpleViewItemGroup;
027import org.ametys.runtime.model.View;
028import org.ametys.runtime.model.ViewHelper;
029import org.ametys.runtime.model.ViewItem;
030import org.ametys.runtime.model.ViewItemAccessor;
031
032/**
033 * Helper for search generator
034 */
035public final class SearchGeneratorHelper
036{
037    private SearchGeneratorHelper()
038    {
039        // Empty constructor
040    }
041    
042    /**
043     * Copy the items of the given accessor, keeping only the items with the given paths
044     * @param viewItemAccessor the view item accessor to copy
045     * @param itemPathsToKeep the paths of the items to keep. If the list is empty, copy the accessor and keep all items
046     * @return the view items accessor's copy
047     */
048    public static ViewItemAccessor copyAndFilterViewItemAccessor(ViewItemAccessor viewItemAccessor, List<String> itemPathsToKeep)
049    {
050        // Copy the given container, but not its children
051        ViewItemAccessor copy;
052        if (viewItemAccessor instanceof ViewItem viewItem)
053        {
054            copy = (ViewItemAccessor) viewItem.createInstance();
055            viewItem.copyTo((ViewItem) copy);
056        }
057        else
058        {
059            copy = new View();
060            ((View) viewItemAccessor).copyTo((View) copy);
061        }
062        
063        for (ViewItem viewItem : viewItemAccessor.getViewItems())
064        {
065            Optional<ViewItem> childCopy = Optional.empty();
066            if (viewItem instanceof ModelViewItem modelViewItem)
067            {
068                if (_shouldKeepModelViewItem(modelViewItem, itemPathsToKeep))
069                {
070                    if (viewItem instanceof ViewItemAccessor childAccessor)
071                    {
072                        childCopy = Optional.of((ViewItem) copyAndFilterViewItemAccessor(childAccessor, itemPathsToKeep));
073                    }
074                    else
075                    {
076                        childCopy = Optional.of(viewItem.createInstance());
077                        viewItem.copyTo(childCopy.get());
078                    }
079                }
080            }
081            else if (viewItem instanceof SimpleViewItemGroup group)
082            {
083                childCopy = Optional.of((ViewItem) copyAndFilterViewItemAccessor(group, itemPathsToKeep));
084            }
085            
086            childCopy.ifPresent(copy::addViewItem);
087        }
088        
089        return copy;
090    }
091    
092    private static boolean _shouldKeepModelViewItem(ModelViewItem modelViewItem, List<String> itemsToKeep)
093    {
094        if (itemsToKeep.isEmpty())
095        {
096            // If no items are specified, keep them all
097            return true;
098        }
099        
100        String modelViewItemPath = ViewHelper.getModelViewItemPath(modelViewItem);
101        for (String itemToKeep : itemsToKeep)
102        {
103            if (Strings.CS.startsWith(itemToKeep, modelViewItemPath))
104            {
105                if (modelViewItem instanceof ViewItemAccessor viewItemAccessor)
106                {
107                    String subItemPath = StringUtils.substring(itemToKeep, modelViewItemPath.length() + 1);
108                    
109                    if (subItemPath.isEmpty() && viewItemAccessor.getViewItems().isEmpty())
110                    {
111                        return true;
112                    }
113                    else if (ViewHelper.hasModelViewItem(viewItemAccessor, subItemPath))
114                    {
115                        ModelViewItem subItem = ViewHelper.getModelViewItem(viewItemAccessor, subItemPath);
116                        if (subItem instanceof ViewItemAccessor subViewItemAccessor && subViewItemAccessor.getViewItems().isEmpty()
117                            || !(subItem instanceof ViewItemAccessor))
118                        {
119                            return true;
120                        }
121                    }
122                }
123                else
124                {
125                    return true;
126                }
127            }
128        }
129        
130        return false;
131    }
132    
133    /**
134     * Removes the items with the given paths from the given accessor
135     * @param viewItemAccessor the view item accessor
136     * @param itemPathsToRemove the paths of the items to remove
137     */
138    public static void removeResultItems(ViewItemAccessor viewItemAccessor, List<String> itemPathsToRemove)
139    {
140        for (String itemPathToRemove : itemPathsToRemove)
141        {
142            Optional<ModelViewItem> modelViewItem = _getModelViewItem(viewItemAccessor, itemPathToRemove);
143            if (modelViewItem.isPresent())
144            {
145                ViewItem itemToRemove = modelViewItem.get();
146                ViewItemAccessor parent = itemToRemove.getParent();
147                while (parent != null)
148                {
149                    parent.removeViewItem(itemToRemove);
150                    
151                    if (parent instanceof ViewItem parentViewItem &&  parent.getViewItems().isEmpty())
152                    {
153                        parent = parentViewItem.getParent();
154                        itemToRemove = parentViewItem;
155                    }
156                    else
157                    {
158                        parent = null;
159                    }
160                }
161            }
162        }
163    }
164    
165    private static Optional<ModelViewItem> _getModelViewItem(ViewItemAccessor viewItemAccessor, String itemPath) throws IllegalArgumentException
166    {
167        String[] pathSegments = StringUtils.split(itemPath, ModelItem.ITEM_PATH_SEPARATOR);
168        
169        if (pathSegments == null || pathSegments.length < 1)
170        {
171            throw new IllegalArgumentException("Unable to retrieve the data at the given path. This path is empty.");
172        }
173        else if (pathSegments.length == 1)
174        {
175            for (ViewItem viewItem : viewItemAccessor.getViewItems())
176            {
177                if (viewItem instanceof ModelViewItem modelViewItem
178                        && _isSearchedModelViewItem(modelViewItem, itemPath))
179                {
180                    return Optional.of(modelViewItem);
181                }
182                else if (viewItem instanceof SimpleViewItemGroup group)
183                {
184                    Optional<ModelViewItem> modelViewItem = _getModelViewItem(group, itemPath);
185                    if (modelViewItem.isPresent())
186                    {
187                        return modelViewItem;
188                    }
189                }
190            }
191        }
192        else
193        {
194            String fisrtSegment = pathSegments[0];
195            String subPath = StringUtils.join(pathSegments, ModelItem.ITEM_PATH_SEPARATOR, 1, pathSegments.length);
196            for (ViewItem viewItem : viewItemAccessor.getViewItems())
197            {
198                if (viewItem instanceof ViewItemAccessor childAccessor)
199                {
200                    Optional<ModelViewItem> modelViewItem = Optional.empty();
201                    if (viewItem instanceof SimpleViewItemGroup)
202                    {
203                        modelViewItem = _getModelViewItem(childAccessor, itemPath);
204                    }
205                    else if (viewItem.getName().equals(fisrtSegment))
206                    {
207                        modelViewItem = _getModelViewItem(childAccessor, subPath);
208                    }
209                    
210                    if (modelViewItem.isPresent())
211                    {
212                        return modelViewItem;
213                    }
214                }
215            }
216        }
217        
218        // no model view item has been found with this path
219        return Optional.empty();
220    }
221    
222    private static boolean _isSearchedModelViewItem(ModelViewItem modelViewItem, String itemName)
223    {
224        return modelViewItem.getName().equals(itemName)
225                && (!(modelViewItem instanceof ViewItemAccessor)
226                        || modelViewItem instanceof ViewItemAccessor childAccessor && childAccessor.getViewItems().isEmpty());
227    }
228}