001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.ade.sitemap.shared;
029
030import org.opencms.ade.detailpage.CmsDetailPageInfo;
031import org.opencms.util.CmsUUID;
032
033import java.io.Serializable;
034import java.util.ArrayList;
035import java.util.Collection;
036import java.util.HashMap;
037import java.util.List;
038import java.util.Map;
039import java.util.stream.Collectors;
040
041/**
042 * A data structure for managing the detail page ordering for different types in a given sitemap.<p>
043 *
044 * @since 8.0.0
045 */
046public class CmsDetailPageTable implements Cloneable, Serializable {
047
048    /** A type indicating the status of a page. */
049    public static enum Status {
050        /** default detail page. */
051        firstDetailPage,
052        /** no detail page. */
053        noDetailPage,
054        /** non-default detail page. */
055        otherDetailPage
056    }
057
058    /** ID for serialization. */
059    private static final long serialVersionUID = -4561142050519767250L;
060
061    /** The detail page info beans indexed by id. */
062    private Map<CmsUUID, CmsDetailPageInfo> m_infoById = new HashMap<CmsUUID, CmsDetailPageInfo>();
063
064    /** The detail page info beans, indexed by type. */
065    private Map<String, List<CmsDetailPageInfo>> m_map = new HashMap<>();
066
067    /**
068     * Creates a detail page table from a list of detail page info bean.<p>
069     *
070     * @param infos the detail page info beans
071     */
072    public CmsDetailPageTable(List<CmsDetailPageInfo> infos) {
073
074        for (CmsDetailPageInfo info : infos) {
075            m_map.compute(info.getType(), (k, vs) -> vs == null ? new ArrayList<>() : vs).add(info);
076            m_infoById.put(info.getId(), info);
077        }
078    }
079
080    /**
081     * Empty default constructor for serialization.<p>
082     */
083    protected CmsDetailPageTable() {
084
085        // for serialization
086    }
087
088    /**
089     * Adds a new detail page information bean to the detail page table.<p>
090     *
091     * @param info the detail page info to add
092     */
093    public void add(CmsDetailPageInfo info) {
094
095        m_map.computeIfAbsent(info.getType(), type -> new ArrayList<>()).add(info);
096        m_infoById.put(info.getId(), info);
097    }
098
099    /**
100     * Checks if the entry for the given id can be made the default detail page entry for its type.
101     *
102     * @param id the id to check
103     * @return true if the entry can be made the default detail page entry
104     */
105    public boolean canMakeDefault(CmsUUID id) {
106
107        if (isDefaultDetailPage(id)) {
108            return false;
109        }
110        CmsDetailPageInfo info = m_infoById.get(id);
111        if ((info == null) || info.isInherited()) {
112            return false;
113        }
114        return true;
115    }
116
117    /**
118     * Returns true if the detail page table contains a page with a given id.<p>
119     *
120     * @param id the page id
121     * @return true if the detail page table contains the page with the given id
122     */
123    public boolean contains(CmsUUID id) {
124
125        return m_infoById.containsKey(id);
126    }
127
128    /**
129     * Copies the detail page table.<p>
130     *
131     * @return the copy of the detail page table
132     */
133    public CmsDetailPageTable copy() {
134
135        List<CmsDetailPageInfo> infos = toList();
136        CmsDetailPageTable result = new CmsDetailPageTable();
137        for (CmsDetailPageInfo info : infos) {
138            result.add(info);
139        }
140        return result;
141    }
142
143    /**
144     * Returns the detail page info for a given page id.<p>
145     *
146     * @param id a page id
147     * @return the detail page info for the given page id
148     */
149    public CmsDetailPageInfo get(CmsUUID id) {
150
151        return m_infoById.get(id);
152    }
153
154    /**
155     * Returns the page ids of all detail pages. <p>
156     *
157     * @return the page ids of all detail pages
158     */
159    public Collection<CmsUUID> getAllIds() {
160
161        return m_infoById.keySet();
162    }
163
164    /**
165     * Returns the list of detail page info beans for a given type.<p>
166     *
167     * @param type the type for which the detail page beans should be retrieved
168     *
169     * @return the detail page beans for that type
170     */
171    public List<CmsDetailPageInfo> getInfosForType(String type) {
172
173        return new ArrayList<CmsDetailPageInfo>(m_map.computeIfAbsent(type, k -> new ArrayList<>()));
174    }
175
176    /**
177     * Returns the page status for the page with the given id.<p>
178     *
179     * @param id the id for which the page status should be checked
180     *
181     * @return the status of the page with the given id
182     */
183    public Status getStatus(CmsUUID id) {
184
185        CmsDetailPageInfo info = m_infoById.get(id);
186        if (info == null) {
187            return Status.noDetailPage;
188        }
189        String type = info.getType();
190        List<CmsDetailPageInfo> pagesWithNoQualifier = m_map.computeIfAbsent(
191            type,
192            k -> new ArrayList<>()).stream().filter(detailPage -> detailPage.getQualifier() == null).collect(
193                Collectors.toList());
194        int index = pagesWithNoQualifier.indexOf(info);
195        // if info has a qualifier, index will be -1, but it's still in the list of other detail pages
196        if (index == 0) {
197            return Status.firstDetailPage;
198        }
199        return Status.otherDetailPage;
200    }
201
202    /**
203     * Returns true if the page with the given id is the default detail page for its type.<p>
204     *
205     * @param id a page id
206     *
207     * @return true if the detail page for the page id is the default detail page
208     */
209    public boolean isDefaultDetailPage(CmsUUID id) {
210
211        CmsDetailPageInfo info = m_infoById.get(id);
212        if (info == null) {
213            return false;
214        }
215        CmsDetailPageInfo firstUnqualifiedEntry = m_map.get(info.getType()).stream().filter(
216            page -> page.getQualifier() == null).findFirst().orElse(null);
217        return (firstUnqualifiedEntry != null) && firstUnqualifiedEntry.getId().equals(id);
218    }
219
220    /**
221     * Moves the detail page information for a given page to the front of the detail pages for the same type.<p>
222     *
223     * @param id a page id
224     *
225     * @return the original position of the detail page entry in the list for the same type
226     */
227    public int makeDefault(CmsUUID id) {
228
229        CmsDetailPageInfo info = m_infoById.get(id);
230        if (info == null) {
231            throw new IllegalArgumentException();
232        }
233
234        CmsDetailPageInfo infoToSave = info;
235        // Making a detail page the default detail page should discard the qualifier
236
237        if (info.getQualifier() != null) {
238            CmsDetailPageInfo info2 = new CmsDetailPageInfo(
239                info.getId(),
240                info.getUri(),
241                info.getType(),
242                null,
243                info.getIconClasses());
244            m_infoById.put(id, info2);
245            if (info.isInherited()) {
246                // this case should be prevented by the GUI, we handle it just to make sure the entry is not saved
247                infoToSave = info2.copyAsInherited();
248            } else {
249                infoToSave = info2;
250            }
251        }
252        String type = info.getType();
253        List<CmsDetailPageInfo> infos = m_map.computeIfAbsent(type, k -> new ArrayList<>());
254        int oldPos = infos.indexOf(info);
255        infos.remove(oldPos);
256        infos.add(0, infoToSave);
257        return oldPos;
258    }
259
260    /**
261     * Removes the detail page with the given id.<p>
262     *
263     * @param id the id of the detail page to remove
264     *
265     * @return the original position of the detail page in the list for its type
266     */
267    public int remove(CmsUUID id) {
268
269        CmsDetailPageInfo info = m_infoById.get(id);
270        if (info == null) {
271            throw new IllegalArgumentException();
272        }
273        String type = info.getType();
274        List<CmsDetailPageInfo> infos = m_map.get(type);
275        int pos = infos.indexOf(info);
276        infos.remove(pos);
277        m_infoById.remove(id);
278        return pos;
279
280    }
281
282    /**
283     * The number of configured detail pages.<p>
284     *
285     * @return the number of detail pages
286     */
287    public int size() {
288
289        return m_infoById.size();
290    }
291
292    /**
293     * Returns a flat list containing all detail pages for all types which preserves the order of detail pages from each type list.<p>
294     *
295     * @return a list of all detail page info beans
296     */
297    public List<CmsDetailPageInfo> toList() {
298
299        List<CmsDetailPageInfo> result = new ArrayList<CmsDetailPageInfo>();
300        for (String key : m_map.keySet()) {
301            for (CmsDetailPageInfo info : m_map.get(key)) {
302                result.add(info);
303            }
304        }
305        return result;
306    }
307
308}