001/*
002 * File   : $Source$
003 * Date   : $Date$
004 * Version: $Revision$
005 *
006 * This library is part of OpenCms -
007 * the Open Source Content Management System
008 *
009 * Copyright (C) 2002 - 2011 Alkacon Software (http://www.alkacon.com)
010 *
011 * This library is free software; you can redistribute it and/or
012 * modify it under the terms of the GNU Lesser General Public
013 * License as published by the Free Software Foundation; either
014 * version 2.1 of the License, or (at your option) any later version.
015 *
016 * This library is distributed in the hope that it will be useful,
017 * but WITHOUT ANY WARRANTY; without even the implied warranty of
018 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019 * Lesser General Public License for more details.
020 *
021 * For further information about Alkacon Software, please see the
022 * company website: http://www.alkacon.com
023 *
024 * For further information about OpenCms, please see the
025 * project website: http://www.opencms.org
026 *
027 * You should have received a copy of the GNU Lesser General Public
028 * License along with this library; if not, write to the Free Software
029 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
030 */
031
032package org.opencms.ade.containerpage.inherited;
033
034import org.opencms.ade.containerpage.shared.CmsInheritanceInfo;
035import org.opencms.file.CmsResource;
036import org.opencms.xml.containerpage.CmsContainerElementBean;
037
038import java.util.ArrayList;
039import java.util.Collections;
040import java.util.HashMap;
041import java.util.HashSet;
042import java.util.List;
043import java.util.Map;
044import java.util.Set;
045
046import com.google.common.collect.Lists;
047
048/**
049 * The state of an inherited container at a given point in the VFS tree.<p>
050 */
051public class CmsInheritedContainerState {
052
053    /** The list of configuration beans for each tree level, from top to bottom. */
054    private List<CmsContainerConfiguration> m_parentConfigurations = new ArrayList<CmsContainerConfiguration>();
055
056    /**
057     * Default constructor.<p>
058     */
059    public CmsInheritedContainerState() {
060
061    }
062
063    /**
064     * Reads the configurations for a root path and its parents from a cache instance and adds them to this state.<p>
065     *
066     * @param cache the cache instance
067     * @param rootPath the root path
068     * @param name the name of the container configuration
069     */
070    public void addConfigurations(CmsContainerConfigurationCache cache, String rootPath, String name) {
071
072        String currentPath = rootPath;
073        List<CmsContainerConfiguration> configurations = new ArrayList<CmsContainerConfiguration>();
074        CmsContainerConfigurationCacheState state = cache.getState();
075        while (currentPath != null) {
076            CmsContainerConfiguration configuration = state.getContainerConfiguration(currentPath, name);
077            if (configuration == null) {
078                configuration = CmsContainerConfiguration.emptyConfiguration();
079            }
080            configuration.setPath(currentPath);
081            configurations.add(configuration);
082            currentPath = CmsResource.getParentFolder(currentPath);
083        }
084        Collections.reverse(configurations);
085        for (CmsContainerConfiguration configuration : configurations) {
086            if (configuration != null) {
087                addConfiguration(configuration);
088            }
089        }
090    }
091
092    /**
093     * Gets a list of container element beans which represent the state of the inherited container.<p>
094     *
095     * The container element beans returned will have additional information available via the getInheritanceInfo method.<p>
096     *
097     * @param includeHidden if true, hidden elements will be included in the result list
098     *
099     * @return the elements for this container state
100     */
101    public List<CmsContainerElementBean> getElements(boolean includeHidden) {
102
103        if (m_parentConfigurations.isEmpty()) {
104            return Collections.emptyList();
105        }
106
107        Map<String, CmsContainerElementBean> elementsByKey = new HashMap<String, CmsContainerElementBean>();
108        for (CmsContainerConfiguration bean : m_parentConfigurations) {
109            elementsByKey.putAll(bean.getNewElements());
110        }
111
112        List<CmsContainerElementBean> result = new ArrayList<CmsContainerElementBean>();
113
114        CmsContainerConfiguration lastElement = m_parentConfigurations.get(m_parentConfigurations.size() - 1);
115        Set<String> newKeys = new HashSet<String>();
116        for (Map.Entry<String, CmsContainerElementBean> entry : lastElement.getNewElements().entrySet()) {
117            String key = entry.getKey();
118            newKeys.add(key);
119        }
120        Set<String> keysUsed = new HashSet<String>();
121        Map<String, String> pathsByKey = new HashMap<String, String>();
122
123        // STEP 1: Get first defined ordering
124        List<String> ordering = null;
125        for (CmsContainerConfiguration configuration : Lists.reverse(m_parentConfigurations)) {
126            if (configuration.getOrdering() != null) {
127                ordering = configuration.getOrdering();
128                break;
129            }
130        }
131        if (ordering == null) {
132            ordering = new ArrayList<String>();
133        }
134        // STEP 2: Get elements which are referenced by the ordering
135        for (String key : ordering) {
136            CmsContainerElementBean element = elementsByKey.get(key);
137            if ((element != null) && !keysUsed.contains(key)) {
138                CmsContainerElementBean elementToAdd = CmsContainerElementBean.cloneWithSettings(
139                    element,
140                    element.getIndividualSettings());
141                CmsInheritanceInfo info = new CmsInheritanceInfo();
142                info.setKey(key);
143                elementToAdd.setInheritanceInfo(info);
144                result.add(elementToAdd);
145                keysUsed.add(key);
146            }
147        }
148        // STEP 3: Add 'new' elements from parents; also fill pathsByKey
149        for (int i = 0; i < (m_parentConfigurations.size()); i++) {
150            CmsContainerConfiguration currentConfig = m_parentConfigurations.get(i);
151            for (Map.Entry<String, CmsContainerElementBean> entry : currentConfig.getNewElementsInOrder().entrySet()) {
152                String key = entry.getKey();
153                pathsByKey.put(key, currentConfig.getPath());
154                if (!keysUsed.contains(key)) {
155                    CmsContainerElementBean elementToAdd = CmsContainerElementBean.cloneWithSettings(
156                        entry.getValue(),
157                        entry.getValue().getIndividualSettings());
158                    CmsInheritanceInfo info = new CmsInheritanceInfo();
159                    info.setKey(key);
160                    elementToAdd.setInheritanceInfo(info);
161                    result.add(elementToAdd);
162                }
163            }
164        }
165        // STEP 4: Determine visibility and new-ness
166        for (CmsContainerElementBean resultElement : result) {
167            CmsInheritanceInfo info = resultElement.getInheritanceInfo();
168            String key = info.getKey();
169            List<Boolean> visibilities = getVisibilities(key);
170            computeVisibility(visibilities, info);
171            info.setIsNew(newKeys.contains(info.getKey()));
172        }
173
174        List<CmsContainerElementBean> resultWithoutHidden = new ArrayList<CmsContainerElementBean>();
175        List<CmsContainerElementBean> hiddenElements = new ArrayList<CmsContainerElementBean>();
176        for (CmsContainerElementBean resultElement : result) {
177            CmsInheritanceInfo info = resultElement.getInheritanceInfo();
178            if (!info.isVisible()) {
179                hiddenElements.add(resultElement);
180            } else {
181                resultWithoutHidden.add(resultElement);
182            }
183        }
184        result = resultWithoutHidden;
185        if (includeHidden) {
186            result.addAll(hiddenElements);
187        }
188        for (CmsContainerElementBean elementBean : result) {
189            CmsInheritanceInfo info = elementBean.getInheritanceInfo();
190            String path = pathsByKey.get(info.getKey());
191            info.setPath(path);
192        }
193
194        return result;
195    }
196
197    /**
198     * Gets the keys of new elements.<p>
199     *
200     * @return a set containing the keys of the new elements
201     */
202    public Set<String> getNewElementKeys() {
203
204        Set<String> result = new HashSet<String>();
205        for (CmsContainerConfiguration configuration : m_parentConfigurations) {
206            result.addAll(configuration.getNewElements().keySet());
207        }
208        return result;
209    }
210
211    /**
212     * Checks whether an element with the given key is actually defined in this inherited container state.<p>
213     *
214     * @param key the key for which the check should be performed
215     *
216     * @return true if an element with the key has been defined in this state
217     */
218    public boolean hasElementWithKey(String key) {
219
220        for (CmsContainerConfiguration configuration : m_parentConfigurations) {
221            if (configuration.getNewElements().containsKey(key)) {
222                return true;
223            }
224        }
225        return false;
226    }
227
228    /**
229     * Adds a configuration bean for a new tree level.<p>
230     *
231     * @param configuration the configuration bean
232     */
233    protected void addConfiguration(CmsContainerConfiguration configuration) {
234
235        m_parentConfigurations.add(configuration);
236
237    }
238
239    /**
240     * Gets the list of visibilities for a given key in all the tree levels.<p>
241     *
242     * @param key the key for which the visibilities should be returned
243     *
244     * @return the list of visibilities, from top to bottom
245     */
246    protected List<Boolean> getVisibilities(String key) {
247
248        List<Boolean> result = new ArrayList<Boolean>();
249        for (CmsContainerConfiguration config : m_parentConfigurations) {
250            result.add(config.getVisibility().get(key));
251        }
252        return result;
253    }
254
255    /**
256     * Computes the visibility for an element.<p>
257     *
258     * @param visibilities the visibilities for the element in the sequence of parent configurations.<p>
259     *
260     * @param info the object in which the visibility should be stored
261     */
262    void computeVisibility(List<Boolean> visibilities, CmsInheritanceInfo info) {
263
264        boolean visible = true;
265        boolean inherited = true;
266        boolean parentVisible = true;
267        for (Boolean visibility : visibilities) {
268            parentVisible = visible;
269            if (visibility == Boolean.TRUE) {
270                visible = true;
271                inherited = false;
272            } else if (visibility == Boolean.FALSE) {
273                visible = false;
274                inherited = false;
275            } else {
276                inherited = true;
277            }
278        }
279        info.setVisible(visible);
280        info.setVisibilityInherited(inherited);
281        info.setParentVisible(parentVisible);
282    }
283}