001/**
002 * Copyright 2005-2014 The Kuali Foundation
003 *
004 * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
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.kuali.rice.krad.uif.view;
017
018import java.io.Serializable;
019import java.util.HashMap;
020import java.util.HashSet;
021import java.util.Map;
022import java.util.Set;
023
024import org.apache.commons.lang.StringUtils;
025import org.apache.log4j.Logger;
026import org.kuali.rice.krad.uif.component.Component;
027import org.kuali.rice.krad.uif.container.CollectionGroup;
028import org.kuali.rice.krad.uif.container.Container;
029import org.kuali.rice.krad.uif.field.DataField;
030import org.kuali.rice.krad.uif.util.CopyUtils;
031import org.kuali.rice.krad.uif.util.LifecycleElement;
032
033/**
034 * Holds component indexes of a {@link View} instance for convenient retrieval during the lifecycle.
035 *
036 * @author Kuali Rice Team (rice.collab@kuali.org)
037 */
038public class ViewIndex implements Serializable {
039    private static final long serialVersionUID = 4700818801272201371L;
040    private static final Logger LOG = Logger.getLogger(ViewIndex.class);
041
042    private Map<String, Component> index;
043    private Map<String, DataField> dataFieldIndex;
044
045    private Map<String, CollectionGroup> collectionsIndex;
046    private Map<String, LifecycleElement> lifecycleElementsByPath;
047
048    private Set<String> assignedIds;
049
050    /**
051     * Default Constructor.
052     */
053    public ViewIndex() {
054        index = new HashMap<String, Component>();
055        dataFieldIndex = new HashMap<String, DataField>();
056        collectionsIndex = new HashMap<String, CollectionGroup>();
057        lifecycleElementsByPath = new HashMap<String, LifecycleElement>();
058        assignedIds = new HashSet<String>();
059    }
060
061    /**
062     * Clears view indexes, for reinitializing indexes at the start of each phase.
063     */
064    protected void clearIndex(View view) {
065        index = new HashMap<String, Component>();
066        dataFieldIndex = new HashMap<String, DataField>();
067        collectionsIndex = new HashMap<String, CollectionGroup>();
068        lifecycleElementsByPath = new HashMap<String, LifecycleElement>();
069    }
070
071    /**
072     * Adds an entry to the main index for the given component. If the component is of type
073     * <code>DataField</code> or <code>CollectionGroup</code> an entry is created in the
074     * corresponding indexes for those types as well. Then the #indexComponent method is called for
075     * each of the component's children
076     *
077     * <p>
078     * If the component is already contained in the indexes, it will be replaced
079     * </p>
080     *
081     * <p>
082     * <code>DataField</code> instances are indexed by the attribute path. This is useful for
083     * retrieving the InputField based on the incoming request parameter
084     * </p>
085     *
086     * <p>
087     * <code>CollectionGroup</code> instances are indexed by the collection path. This is useful for
088     * retrieving the CollectionGroup based on the incoming request parameter
089     * </p>
090     *
091     * @param component component instance to index
092     */
093    public void indexComponent(Component component) {
094        if (component == null) {
095            return;
096        }
097
098        synchronized (index) {
099            index.put(component.getId(), component);
100        }
101
102        synchronized (lifecycleElementsByPath) {
103            lifecycleElementsByPath.put(component.getViewPath(), component);
104        }
105
106        if (component instanceof Container) {
107            Container container = (Container) component;
108            if (container.getLayoutManager() != null) {
109                synchronized (lifecycleElementsByPath) {
110                    lifecycleElementsByPath.put(container.getLayoutManager().getViewPath(),
111                            container.getLayoutManager());
112                }
113            }
114        }
115
116        if (component instanceof DataField) {
117            DataField field = (DataField) component;
118
119            synchronized (dataFieldIndex) {
120                dataFieldIndex.put(field.getBindingInfo().getBindingPath(), field);
121            }
122        } else if (component instanceof CollectionGroup) {
123            CollectionGroup collectionGroup = (CollectionGroup) component;
124
125            synchronized (collectionsIndex) {
126                collectionsIndex.put(collectionGroup.getBindingInfo().getBindingPath(), collectionGroup);
127            }
128        }
129    }
130
131    /**
132     * Observe an assigned ID.
133     *
134     * @param id ID to observe
135     * @return true if the ID is unique, false if the ID has already been observed
136     */
137    public boolean observeAssignedId(String id) {
138        if (assignedIds.contains(id)) {
139            return false;
140        }
141
142        synchronized (assignedIds) {
143            return assignedIds.add(id);
144        }
145    }
146
147    /**
148     * Retrieves a <code>Component</code> from the view index by Id
149     *
150     * @param id id for the component to retrieve
151     * @return Component instance found in index, or null if no such component exists
152     */
153    public Component getComponentById(String id) {
154        return index.get(id);
155    }
156
157    /**
158     * Retrieves a <code>DataField</code> instance from the index
159     *
160     * @param propertyPath full path of the data field (from the form)
161     * @return DataField instance for the path or Null if not found
162     */
163    public DataField getDataFieldByPath(String propertyPath) {
164        return dataFieldIndex.get(propertyPath);
165    }
166
167    /**
168     * Retrieves a <code>DataField</code> instance that has the given property name specified (note
169     * this is not the full binding path and first match is returned)
170     *
171     * @param propertyName property name for field to retrieve
172     * @return DataField instance found or null if not found
173     */
174    public DataField getDataFieldByPropertyName(String propertyName) {
175        DataField dataField = null;
176
177        for (DataField field : dataFieldIndex.values()) {
178            if (StringUtils.equals(propertyName, field.getPropertyName())) {
179                dataField = field;
180                break;
181            }
182        }
183
184        return dataField;
185    }
186
187    /**
188     * Gets the Map of lifecycle elements that are indexed by their path relative to the view.
189     *
190     * @return map of all indexed lifecycle elements, key is the element path and value is the element instance
191     */
192    public Map<String, LifecycleElement> getLifecycleElementsByPath() {
193        return lifecycleElementsByPath;
194    }
195
196    /**
197     * Gets a lifecycle element instance by the given path (relative to the view).
198     *
199     * @param path path of the element that should be returned
200     * @return lifecycle element instance for given path or null if element at that path does not exist
201     */
202    public LifecycleElement getLifecycleElementByPath(String path) {
203        if ((lifecycleElementsByPath != null) && lifecycleElementsByPath.containsKey(path)) {
204            return lifecycleElementsByPath.get(path);
205        }
206
207        return null;
208    }
209
210    /**
211     * Gets the Map that contains attribute field indexing information. The Map key points to an
212     * attribute binding path, and the Map value is the <code>DataField</code> instance
213     *
214     * @return data fields index map
215     */
216    public Map<String, DataField> getDataFieldIndex() {
217        return this.dataFieldIndex;
218    }
219
220    /**
221     * Gets the Map that contains collection indexing information. The Map key gives the binding
222     * path to the collection, and the Map value givens the <code>CollectionGroup</code> instance
223     *
224     * @return collection index map
225     */
226    public Map<String, CollectionGroup> getCollectionsIndex() {
227        return this.collectionsIndex;
228    }
229
230    /**
231     * Retrieves a <code>CollectionGroup</code> instance from the index
232     *
233     * @param collectionPath full path of the collection (from the form)
234     * @return CollectionGroup instance for the collection path or Null if not found
235     */
236    public CollectionGroup getCollectionGroupByPath(String collectionPath) {
237        return collectionsIndex.get(collectionPath);
238    }
239
240    /**
241     * Returns a clone of the view index.
242     *
243     * @return ViewIndex clone
244     */
245    public ViewIndex copy() {
246        ViewIndex viewIndexCopy = new ViewIndex();
247
248        if (this.index != null) {
249            Map<String, Component> indexCopy = new HashMap<String, Component>();
250            for (Map.Entry<String, Component> indexEntry : this.index.entrySet()) {
251                if (indexEntry.getValue() instanceof View) {
252                    LOG.warn("View reference at " + indexEntry);
253                } else {
254                    indexCopy.put(indexEntry.getKey(), (Component) CopyUtils.copy(indexEntry.getValue()));
255                }
256            }
257
258            viewIndexCopy.index = indexCopy;
259        }
260
261        if (this.dataFieldIndex != null) {
262            Map<String, DataField> dataFieldIndexCopy = new HashMap<String, DataField>();
263            for (Map.Entry<String, DataField> indexEntry : this.dataFieldIndex.entrySet()) {
264                dataFieldIndexCopy.put(indexEntry.getKey(), (DataField) CopyUtils.copy(indexEntry.getValue()));
265            }
266
267            viewIndexCopy.dataFieldIndex = dataFieldIndexCopy;
268        }
269
270        if (this.collectionsIndex != null) {
271            Map<String, CollectionGroup> collectionsIndexCopy = new HashMap<String, CollectionGroup>();
272            for (Map.Entry<String, CollectionGroup> indexEntry : this.collectionsIndex.entrySet()) {
273                collectionsIndexCopy.put(indexEntry.getKey(), (CollectionGroup) CopyUtils.copy(indexEntry.getValue()));
274            }
275
276            viewIndexCopy.collectionsIndex = collectionsIndexCopy;
277        }
278
279        return viewIndexCopy;
280    }
281
282}