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 }