001 /*
002 * JBoss DNA (http://www.jboss.org/dna)
003 * See the COPYRIGHT.txt file distributed with this work for information
004 * regarding copyright ownership. Some portions may be licensed
005 * to Red Hat, Inc. under one or more contributor license agreements.
006 * See the AUTHORS.txt file in the distribution for a full listing of
007 * individual contributors.
008 *
009 * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
010 * is licensed to you under the terms of the GNU Lesser General Public License as
011 * published by the Free Software Foundation; either version 2.1 of
012 * the License, or (at your option) any later version.
013 *
014 * JBoss DNA is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017 * Lesser General Public License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this software; if not, write to the Free
021 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023 */
024 package org.jboss.dna.repository.sequencer;
025
026 import java.util.ArrayList;
027 import java.util.Collections;
028 import java.util.HashMap;
029 import java.util.Iterator;
030 import java.util.LinkedList;
031 import java.util.List;
032 import java.util.Map;
033 import net.jcip.annotations.Immutable;
034 import net.jcip.annotations.NotThreadSafe;
035 import org.jboss.dna.common.util.CheckArg;
036 import org.jboss.dna.graph.JcrLexicon;
037 import org.jboss.dna.graph.property.Name;
038 import org.jboss.dna.graph.property.Path;
039 import org.jboss.dna.graph.property.PathFactory;
040 import org.jboss.dna.graph.property.ValueFactories;
041 import org.jboss.dna.graph.sequencer.SequencerOutput;
042
043 /**
044 * A basic {@link SequencerOutput} that records all information in-memory and which organizes the properties by {@link Path node
045 * paths} and provides access to the nodes in a natural path-order.
046 *
047 * @author Randall Hauch
048 * @author John Verhaeg
049 */
050 @NotThreadSafe
051 public class SequencerOutputMap implements SequencerOutput, Iterable<SequencerOutputMap.Entry> {
052
053 private static final String JCR_NAME_PROPERTY_NAME = "jcr:name";
054
055 private final Map<Path, List<PropertyValue>> data;
056 private transient boolean valuesSorted = true;
057 private final ValueFactories factories;
058 private final Name jcrName;
059
060 public SequencerOutputMap( ValueFactories factories ) {
061 CheckArg.isNotNull(factories, "factories");
062 this.data = new HashMap<Path, List<PropertyValue>>();
063 this.factories = factories;
064 this.jcrName = this.factories.getNameFactory().create(JCR_NAME_PROPERTY_NAME);
065 }
066
067 ValueFactories getFactories() {
068 return this.factories;
069 }
070
071 /**
072 * {@inheritDoc}
073 */
074 public void setProperty( Path nodePath,
075 Name propertyName,
076 Object... values ) {
077 CheckArg.isNotNull(nodePath, "nodePath");
078 CheckArg.isNotNull(propertyName, "property");
079 // Ignore the "jcr:name" property, as that's handled by the path ...
080 if (this.jcrName.equals(propertyName)) return;
081
082 // Find or create the entry for this node ...
083 List<PropertyValue> properties = this.data.get(nodePath);
084 if (properties == null) {
085 if (values == null || values.length == 0) return; // do nothing
086 properties = new ArrayList<PropertyValue>();
087 this.data.put(nodePath, properties);
088 }
089 if (values == null || values.length == 0) {
090 properties.remove(new PropertyValue(propertyName, null));
091 } else {
092 Object propValue = values.length == 1 ? values[0] : values;
093 PropertyValue value = new PropertyValue(propertyName, propValue);
094 properties.add(value);
095 valuesSorted = false;
096 }
097 }
098
099 /**
100 * {@inheritDoc}
101 */
102 public void setProperty( String nodePath,
103 String property,
104 Object... values ) {
105 CheckArg.isNotEmpty(nodePath, "nodePath");
106 CheckArg.isNotEmpty(property, "property");
107 Path path = this.factories.getPathFactory().create(nodePath);
108 Name propertyName = this.factories.getNameFactory().create(property);
109 setProperty(path, propertyName, values);
110 }
111
112 /**
113 * {@inheritDoc}
114 */
115 public void setReference( String nodePath,
116 String propertyName,
117 String... paths ) {
118 PathFactory pathFactory = this.factories.getPathFactory();
119 Path path = pathFactory.create(nodePath);
120 Name name = this.factories.getNameFactory().create(propertyName);
121 Object[] values = null;
122 if (paths != null && paths.length != 0) {
123 values = new Path[paths.length];
124 for (int i = 0, len = paths.length; i != len; ++i) {
125 String pathValue = paths[i];
126 values[i] = pathFactory.create(pathValue);
127 }
128 }
129 setProperty(path, name, values);
130 }
131
132 /**
133 * Return the number of node entries in this map.
134 *
135 * @return the number of entries
136 */
137 public int size() {
138 return this.data.size();
139 }
140
141 /**
142 * Return whether there are no entries
143 *
144 * @return true if this container is empty, or false otherwise
145 */
146 public boolean isEmpty() {
147 return this.data.isEmpty();
148 }
149
150 protected List<PropertyValue> removeProperties( Path nodePath ) {
151 return this.data.remove(nodePath);
152 }
153
154 /**
155 * Get the properties for the node given by the supplied path.
156 *
157 * @param nodePath the path to the node
158 * @return the property values, or null if there are none
159 */
160 protected List<PropertyValue> getProperties( Path nodePath ) {
161 return data.get(nodePath);
162 }
163
164 /**
165 * Return the entries in this output in an order with shorter paths first.
166 * <p>
167 * {@inheritDoc}
168 */
169 public Iterator<Entry> iterator() {
170 LinkedList<Path> paths = new LinkedList<Path>(data.keySet());
171 Collections.sort(paths);
172 sortValues();
173 return new EntryIterator(paths.iterator());
174 }
175
176 protected void sortValues() {
177 if (!valuesSorted) {
178 for (List<PropertyValue> values : this.data.values()) {
179 if (values.size() > 1) Collections.sort(values);
180 }
181 valuesSorted = true;
182 }
183 }
184
185 /**
186 * {@inheritDoc}
187 */
188 @Override
189 public String toString() {
190 return this.data.toString();
191 }
192
193 /**
194 * A property name and value pair. PropertyValue instances have a natural order where the <code>jcr:primaryType</code> is
195 * first, followed by all other properties in ascending lexicographical order according to the {@link #getName() name}.
196 *
197 * @author Randall Hauch
198 */
199 @Immutable
200 public class PropertyValue implements Comparable<PropertyValue> {
201
202 private final Name name;
203 private final Object value;
204
205 protected PropertyValue( Name propertyName,
206 Object value ) {
207 this.name = propertyName;
208 this.value = value;
209 }
210
211 /**
212 * Get the property name.
213 *
214 * @return the property name; never null
215 */
216 public Name getName() {
217 return this.name;
218 }
219
220 /**
221 * Get the property value, which is either a single value or an array of values.
222 *
223 * @return the property value
224 */
225 public Object getValue() {
226 return this.value;
227 }
228
229 /**
230 * {@inheritDoc}
231 */
232 public int compareTo( PropertyValue that ) {
233 if (this == that) return 0;
234 if (this.name.equals(JcrLexicon.PRIMARY_TYPE)) return -1;
235 if (that.name.equals(JcrLexicon.PRIMARY_TYPE)) return 1;
236 return this.name.compareTo(that.name);
237 }
238
239 /**
240 * {@inheritDoc}
241 */
242 @Override
243 public int hashCode() {
244 return this.name.hashCode();
245 }
246
247 /**
248 * {@inheritDoc}
249 */
250 @Override
251 public boolean equals( Object obj ) {
252 if (obj == this) return true;
253 if (obj instanceof PropertyValue) {
254 PropertyValue that = (PropertyValue)obj;
255 if (!this.getName().equals(that.getName())) return false;
256 return true;
257 }
258 return false;
259 }
260
261 /**
262 * {@inheritDoc}
263 */
264 @Override
265 public String toString() {
266 return "[" + this.name + "=" + value + "]";
267 }
268 }
269
270 /**
271 * An entry in a SequencerOutputMap, which contains the path of the node and the {@link #getPropertyValues() property values}
272 * on the node.
273 *
274 * @author Randall Hauch
275 */
276 @Immutable
277 public class Entry {
278
279 private final Path path;
280 private final Name primaryType;
281 private final List<PropertyValue> properties;
282
283 protected Entry( Path path,
284 List<PropertyValue> properties ) {
285 assert path != null;
286 assert properties != null;
287 this.path = path;
288 this.properties = properties;
289 if (this.properties.size() > 0 && this.properties.get(0).getName().equals("jcr:primaryType")) {
290 PropertyValue primaryTypeProperty = this.properties.remove(0);
291 this.primaryType = getFactories().getNameFactory().create(primaryTypeProperty.getValue());
292 } else {
293 this.primaryType = null;
294 }
295 }
296
297 /**
298 * @return path
299 */
300 public Path getPath() {
301 return this.path;
302 }
303
304 /**
305 * Get the primary type specified for this node, or null if the type was not specified
306 *
307 * @return the primary type, or null
308 */
309 public Name getPrimaryTypeValue() {
310 return this.primaryType;
311 }
312
313 /**
314 * Get the property values, which may be empty
315 *
316 * @return value
317 */
318 public List<PropertyValue> getPropertyValues() {
319 return getProperties(path);
320 }
321 }
322
323 protected class EntryIterator implements Iterator<Entry> {
324
325 private Path last;
326 private final Iterator<Path> iter;
327
328 protected EntryIterator( Iterator<Path> iter ) {
329 this.iter = iter;
330 }
331
332 /**
333 * {@inheritDoc}
334 */
335 public boolean hasNext() {
336 return iter.hasNext();
337 }
338
339 /**
340 * {@inheritDoc}
341 */
342 public Entry next() {
343 this.last = iter.next();
344 return new Entry(last, getProperties(last));
345 }
346
347 /**
348 * {@inheritDoc}
349 */
350 public void remove() {
351 if (last == null) throw new IllegalStateException();
352 try {
353 removeProperties(last);
354 } finally {
355 last = null;
356 }
357 }
358 }
359
360 }