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