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 GmbH & Co. KG, 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.workplace.tools;
029
030import java.util.ArrayList;
031import java.util.Collections;
032import java.util.HashMap;
033import java.util.Iterator;
034import java.util.List;
035import java.util.Map;
036
037/**
038 * Default implementation of a named object container. <p>
039 *
040 * It can handle relative or absolute orderings and unique names.<p>
041 *
042 * @param <T> the type of objects
043 *
044 * @since 6.0.0
045 */
046public class CmsIdentifiableObjectContainer<T> {
047
048    /**
049     * Internal class just for taking care of the positions in the container.<p>
050     *
051     * @param <T> the object type
052     *
053     * @since 6.0.0
054     */
055    private static class CmsIdObjectElement<T> {
056
057        /** Identifiable object. */
058        private final T m_object;
059
060        /** Relative position. */
061        private final float m_position;
062
063        /**
064         * Default Constructor.<p>
065         *
066         * @param object the object
067         * @param position the relative position
068         *
069         */
070        public CmsIdObjectElement(T object, float position) {
071
072            m_object = object;
073            m_position = position;
074        }
075
076        /**
077         * Returns the object.<p>
078         *
079         * @return the object
080         */
081        public T getObject() {
082
083            return m_object;
084        }
085
086        /**
087         * Returns the position.<p>
088         *
089         * @return the position
090         */
091        public float getPosition() {
092
093            return m_position;
094        }
095
096    }
097
098    /** Cache for element list. */
099    private List<T> m_cache;
100
101    /** List of objects. */
102    private final List<T> m_objectList = new ArrayList<T>();
103
104    /** Map of objects only used if uniqueIds flag set. */
105    private final Map<String, T> m_objectsById = new HashMap<String, T>();
106
107    /** Map of object lists by id. */
108    private final Map<String, List<T>> m_objectsListsById = new HashMap<String, List<T>>();
109
110    /** List of ordered objects. */
111    private final List<CmsIdObjectElement<T>> m_orderedObjectList = new ArrayList<CmsIdObjectElement<T>>();
112
113    /** Flag for managing absolute and relative ordering. */
114    private final boolean m_relativeOrdered;
115
116    /** Flag for managing uniqueness check. */
117    private final boolean m_uniqueIds;
118
119    /**
120     * Default Constructor.<p>
121     *
122     * @param uniqueIds if the list show check for unique ids
123     * @param relativeOrdered if the list show use relative ordering, instead of absolute ordering
124     */
125    public CmsIdentifiableObjectContainer(boolean uniqueIds, boolean relativeOrdered) {
126
127        m_uniqueIds = uniqueIds;
128        m_relativeOrdered = relativeOrdered;
129    }
130
131    /**
132     * Appends the specified object to the end of this container. <p>
133     *
134     * @param id the object identifier
135     * @param idObject the object add to the container
136     *
137     * @see java.util.List#add(Object)
138     */
139    public void addIdentifiableObject(String id, T idObject) {
140
141        m_cache = null;
142        if (m_uniqueIds && (m_objectsById.get(id) != null)) {
143            removeObject(id);
144        }
145        if (m_relativeOrdered) {
146            float pos = 1;
147            if (!m_orderedObjectList.isEmpty()) {
148                pos = m_orderedObjectList.get(m_orderedObjectList.size() - 1).getPosition() + 1;
149            }
150            m_orderedObjectList.add(new CmsIdObjectElement<T>(idObject, pos));
151        } else {
152            m_objectList.add(idObject);
153        }
154        if (m_uniqueIds) {
155            m_objectsById.put(id, idObject);
156        } else {
157            List<T> prevObj = m_objectsListsById.get(id);
158            if (prevObj == null) {
159                List<T> list = new ArrayList<T>();
160                list.add(idObject);
161                m_objectsListsById.put(id, list);
162            } else {
163                prevObj.add(idObject);
164            }
165        }
166
167    }
168
169    /**
170     * Inserts the specified object at the specified position in this container.<p>
171     *
172     * Shifts the object currently at that position (if any) and any subsequent
173     * objects to the right (adds one to their indices).<p>
174     *
175     * @param id the object identifier
176     * @param idObject the object add to the container
177     * @param position the insertion point
178     *
179     * @see java.util.List#add(int, Object)
180     */
181    public void addIdentifiableObject(String id, T idObject, float position) {
182
183        m_cache = null;
184        if (m_uniqueIds && (m_objectsById.get(id) != null)) {
185            removeObject(id);
186        }
187        if (m_relativeOrdered) {
188            int pos = 0;
189            Iterator<CmsIdObjectElement<T>> itElems = m_orderedObjectList.iterator();
190            while (itElems.hasNext()) {
191                CmsIdObjectElement<T> element = itElems.next();
192                if (element.getPosition() > position) {
193                    break;
194                }
195                pos++;
196            }
197            m_orderedObjectList.add(pos, new CmsIdObjectElement<T>(idObject, position));
198        } else {
199            m_objectList.add((int)position, idObject);
200        }
201        if (m_uniqueIds) {
202            m_objectsById.put(id, idObject);
203        } else {
204            List<T> prevObj = m_objectsListsById.get(id);
205            if (prevObj == null) {
206                List<T> list = new ArrayList<T>();
207                list.add(idObject);
208                m_objectsListsById.put(id, list);
209            } else {
210                prevObj.add(idObject);
211            }
212        }
213
214    }
215
216    /**
217     * Resets the container.<p>
218     */
219    public void clear() {
220
221        m_cache = null;
222        m_objectList.clear();
223        m_objectsById.clear();
224        m_orderedObjectList.clear();
225        m_objectsListsById.clear();
226    }
227
228    /**
229     * Returns the list of objects.<p>
230     *
231     * @return the a list of <code>{@link Object}</code>s.
232     */
233    public List<T> elementList() {
234
235        if (m_cache != null) {
236            return m_cache;
237        }
238        if (m_relativeOrdered) {
239            List<T> objectList = new ArrayList<T>();
240            Iterator<CmsIdObjectElement<T>> itObjs = m_orderedObjectList.iterator();
241            while (itObjs.hasNext()) {
242                CmsIdObjectElement<T> object = itObjs.next();
243                objectList.add(object.getObject());
244            }
245            m_cache = Collections.unmodifiableList(objectList);
246        } else {
247            m_cache = Collections.unmodifiableList(m_objectList);
248        }
249        return m_cache;
250    }
251
252    /**
253     * Returns the object with the given id.<p>
254     *
255     * If <code>uniqueIds</code> is set to <code>false</code> an <code>{@link Object}</code>
256     * containing a <code>{@link List}</code> with all the objects with the given id is returned.<p>
257     *
258     * If the container no contains any object with the given id, <code>null</code> is returned.<p>
259     *
260     * @param id the id of the object
261     *
262     * @return the object if found, or <code>null</code>
263     *
264     * @see java.util.Map#get(Object)
265     */
266    public T getObject(String id) {
267
268        if (!m_uniqueIds) {
269            throw new UnsupportedOperationException("Not supported for not unique ids");
270        }
271        return m_objectsById.get(id);
272
273    }
274
275    /**
276     * Returns the list of objects with the given id.<p>
277     *
278     * @param id the object id
279     *
280     * @return the list of objects if found, or <code>null</code>
281     */
282    public List<T> getObjectList(String id) {
283
284        if (m_uniqueIds) {
285            throw new UnsupportedOperationException("Not supported for unique ids");
286        }
287        return m_objectsListsById.get(id);
288    }
289
290    /**
291     * Removes an object with the given id.<p>
292     *
293     * if <code>m_uniqueIds</code> is set, it will remove at most one object.
294     * otherwise it will remove all elements with the given id.<p>
295     *
296     * @param id the id of the object to remove
297     */
298    public synchronized void removeObject(String id) {
299
300        m_cache = null;
301        if (m_relativeOrdered) {
302            if (m_uniqueIds) {
303                Object o = getObject(id);
304                Iterator<CmsIdObjectElement<T>> itObjs = m_orderedObjectList.iterator();
305                while (itObjs.hasNext()) {
306                    CmsIdObjectElement<T> object = itObjs.next();
307                    if (object.getObject() == o) {
308                        itObjs.remove();
309                        break;
310                    }
311                }
312                m_objectsById.remove(id);
313            } else {
314                Iterator<T> itRemove = m_objectsListsById.get(id).iterator();
315                while (itRemove.hasNext()) {
316                    T o = itRemove.next();
317                    Iterator<CmsIdObjectElement<T>> itObjs = m_orderedObjectList.iterator();
318                    while (itObjs.hasNext()) {
319                        CmsIdObjectElement<T> object = itObjs.next();
320                        if (object.getObject() == o) {
321                            itObjs.remove();
322                            break;
323                        }
324                    }
325                }
326                m_orderedObjectList.remove(id);
327            }
328        } else {
329            Object o = getObject(id);
330            m_objectList.remove(o);
331            if (m_uniqueIds) {
332                m_objectsById.remove(id);
333            } else {
334                m_objectsListsById.remove(id);
335            }
336        }
337    }
338}