View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.orchestra.dynaForm.metadata.impl;
20  
21  import java.util.Collections;
22  import java.util.Iterator;
23  import java.util.LinkedHashMap;
24  import java.util.Map;
25  import java.util.Set;
26  import java.util.TreeSet;
27  
28  import org.apache.myfaces.orchestra.dynaForm.metadata.MetaDataWritable;
29  import org.apache.myfaces.orchestra.dynaForm.metadata.MetaField;
30  
31  /**
32   * A convenience implementation of the MetaDataWritable interface.
33   * <p>
34   * An instance of this type can be created and then passed to a list of one or
35   * more Extractor objects, together with a source object to be introspected.
36   * The extractor instances deduce information about fields of the source
37   * object and then add or update MetaField objects held by the MetaDataImpl.
38   * <p>
39   * The resulting populated object is then typically cast to the read-only
40   * MetaData interface type and passed to objects that make use of the 
41   * gathered metadata, such as dynaform "gui builders". 
42   */
43  public class MetaDataImpl implements MetaDataWritable
44  {
45      private final Set<String> requestedFields = new TreeSet<String>();
46      private final Set<String> requestedFieldParents = new TreeSet<String>();
47      
48      // Here, a LinkedHashMap is used to ensure that fields can be iterated over
49      // in the order in which they were added to the map.
50      private final Map<String, MetaFieldImpl> fields = new LinkedHashMap<String, MetaFieldImpl>();
51  
52      private boolean lockFields = false;
53  
54      public MetaDataImpl()
55      {
56      }
57  
58      /**
59       * Indicates whether metadata about this field is wanted.
60       * <p>
61       * If this object is not "locked", then this always returns true.
62       * <p>
63       * Even when locked, this returns true if:
64       * <ul>
65       * <li>The field is already added to this object, or
66       * <li>The field has explicitly been "requested", or
67       * <li>The name is of form "foo.bar", and "foo.bar.baz" has
68       *  been added to the "requested" fields. 
69       * </ul>
70       *
71       * @see #setLockFields(boolean)
72       */
73      public boolean isWantedField(String name)
74      {
75          return !lockFields 
76              || isParentOfWantedField(name)
77              || requestedFields.contains(name)
78              || fields.containsKey(name);
79      }
80  
81      /**
82       * Is metadata about this field wanted because some child field of this
83       * field has explicitly been marked as requested?
84       *
85       * @return true if the given name is the parent of one of the requestedFields
86       * @see #processField(String)
87       * @see #setLockFields(boolean)
88       */
89      public boolean isParentOfWantedField(String name)
90      {
91          return requestedFieldParents.contains(name);
92      }
93  
94      /**
95       * Allow a field to be added to this object even after this MetaData object is
96       * "locked" for field addition.
97       * <p>
98       * This is used when traversing the object graph for linked entities.
99       * <p>
100      * When a name like "foo.bar.baz" is passed to this method, the requestedFieldsParent
101      * list has "foo" and "foo.bar" added to it. 
102      */
103     public void requestField(String name)
104     {
105         int currIndex = name.indexOf('.');
106         while (currIndex > -1)
107         {
108             String key = name.substring(0, currIndex);
109             if (!requestedFieldParents.contains(key))
110             {
111                 requestedFieldParents.add(key);
112             }
113             currIndex = name.indexOf('.', currIndex + 1);
114         }
115 
116         requestedFields.add(name);
117     }
118 
119     public Set<String> getRequestedFields()
120     {
121         return Collections.unmodifiableSet(requestedFields);
122     }
123 
124     /**
125      * Add a new field to the metadata or return one if one already exists for
126      * the given name
127      */
128     public MetaFieldImpl getOrCreateField(String name)
129     {
130         if (!isWantedField(name))
131         {
132             throw new SecurityException("Current state do not allow to add the field: " + name);
133         }
134 
135         MetaFieldImpl field = fields.get(name);
136         if (field == null)
137         {
138             field = new MetaFieldImpl(name);
139             fields.put(name, field);
140         }
141         return field;
142     }
143 
144     public int getFieldCount()
145     {
146         return fields.size();
147     }
148 
149     public Iterator<String> iterFieldNames()
150     {
151         return fields.keySet().iterator();
152     }
153 
154     public MetaField getField(String name)
155     {
156         return fields.get(name);
157     }
158 
159     public String[] getFieldNames()
160     {
161         return fields.keySet().toArray(new String[fields.size()]);
162     }
163 
164     /**
165      * if set to true this avoids any field to be newly created, only already existent fields are to be processed
166      */
167     public boolean setLockFields(boolean lockFields)
168     {
169         boolean prev = this.lockFields;
170         this.lockFields = lockFields;
171         return prev;
172     }
173 }