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