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 final Map<Path, List<PropertyValue>> data;
054 private transient boolean valuesSorted = true;
055 private final ValueFactories factories;
056
057 public SequencerOutputMap( ValueFactories factories ) {
058 CheckArg.isNotNull(factories, "factories");
059 this.data = new HashMap<Path, List<PropertyValue>>();
060 this.factories = factories;
061 }
062
063 ValueFactories getFactories() {
064 return this.factories;
065 }
066
067 /**
068 * {@inheritDoc}
069 */
070 public void setProperty( Path nodePath,
071 Name propertyName,
072 Object... values ) {
073 CheckArg.isNotNull(nodePath, "nodePath");
074 CheckArg.isNotNull(propertyName, "property");
075
076 // Find or create the entry for this node ...
077 List<PropertyValue> properties = this.data.get(nodePath);
078 if (properties == null) {
079 if (values == null || values.length == 0) return; // do nothing
080 properties = new ArrayList<PropertyValue>();
081 this.data.put(nodePath, properties);
082 }
083 if (values == null || values.length == 0) {
084 properties.remove(new PropertyValue(propertyName, null));
085 } else {
086 Object propValue = values.length == 1 ? values[0] : values;
087 PropertyValue value = new PropertyValue(propertyName, propValue);
088 properties.add(value);
089 valuesSorted = false;
090 }
091 }
092
093 /**
094 * {@inheritDoc}
095 */
096 public void setProperty( String nodePath,
097 String property,
098 Object... values ) {
099 CheckArg.isNotEmpty(nodePath, "nodePath");
100 CheckArg.isNotEmpty(property, "property");
101 Path path = this.factories.getPathFactory().create(nodePath);
102 Name propertyName = this.factories.getNameFactory().create(property);
103 setProperty(path, propertyName, values);
104 }
105
106 /**
107 * {@inheritDoc}
108 */
109 public void setReference( String nodePath,
110 String propertyName,
111 String... paths ) {
112 PathFactory pathFactory = this.factories.getPathFactory();
113 Path path = pathFactory.create(nodePath);
114 Name name = this.factories.getNameFactory().create(propertyName);
115 Object[] values = null;
116 if (paths != null && paths.length != 0) {
117 values = new Path[paths.length];
118 for (int i = 0, len = paths.length; i != len; ++i) {
119 String pathValue = paths[i];
120 values[i] = pathFactory.create(pathValue);
121 }
122 }
123 setProperty(path, name, values);
124 }
125
126 /**
127 * Return the number of node entries in this map.
128 *
129 * @return the number of entries
130 */
131 public int size() {
132 return this.data.size();
133 }
134
135 /**
136 * Return whether there are no entries
137 *
138 * @return true if this container is empty, or false otherwise
139 */
140 public boolean isEmpty() {
141 return this.data.isEmpty();
142 }
143
144 protected List<PropertyValue> removeProperties( Path nodePath ) {
145 return this.data.remove(nodePath);
146 }
147
148 /**
149 * Get the properties for the node given by the supplied path.
150 *
151 * @param nodePath the path to the node
152 * @return the property values, or null if there are none
153 */
154 protected List<PropertyValue> getProperties( Path nodePath ) {
155 return data.get(nodePath);
156 }
157
158 /**
159 * Return the entries in this output in an order with shorter paths first.
160 * <p>
161 * {@inheritDoc}
162 */
163 public Iterator<Entry> iterator() {
164 LinkedList<Path> paths = new LinkedList<Path>(data.keySet());
165 Collections.sort(paths);
166 sortValues();
167 return new EntryIterator(paths.iterator());
168 }
169
170 protected void sortValues() {
171 if (!valuesSorted) {
172 for (List<PropertyValue> values : this.data.values()) {
173 if (values.size() > 1) Collections.sort(values);
174 }
175 valuesSorted = true;
176 }
177 }
178
179 /**
180 * {@inheritDoc}
181 */
182 @Override
183 public String toString() {
184 return this.data.toString();
185 }
186
187 /**
188 * A property name and value pair. PropertyValue instances have a natural order where the <code>jcr:primaryType</code> is
189 * first, followed by all other properties in ascending lexicographical order according to the {@link #getName() name}.
190 *
191 * @author Randall Hauch
192 */
193 @Immutable
194 public class PropertyValue implements Comparable<PropertyValue> {
195
196 private final Name name;
197 private final Object value;
198
199 protected PropertyValue( Name propertyName,
200 Object value ) {
201 this.name = propertyName;
202 this.value = value;
203 }
204
205 /**
206 * Get the property name.
207 *
208 * @return the property name; never null
209 */
210 public Name getName() {
211 return this.name;
212 }
213
214 /**
215 * Get the property value, which is either a single value or an array of values.
216 *
217 * @return the property value
218 */
219 public Object getValue() {
220 return this.value;
221 }
222
223 /**
224 * {@inheritDoc}
225 */
226 public int compareTo( PropertyValue that ) {
227 if (this == that) return 0;
228 if (this.name.equals(JcrLexicon.PRIMARY_TYPE)) return -1;
229 if (that.name.equals(JcrLexicon.PRIMARY_TYPE)) return 1;
230 return this.name.compareTo(that.name);
231 }
232
233 /**
234 * {@inheritDoc}
235 */
236 @Override
237 public int hashCode() {
238 return this.name.hashCode();
239 }
240
241 /**
242 * {@inheritDoc}
243 */
244 @Override
245 public boolean equals( Object obj ) {
246 if (obj == this) return true;
247 if (obj instanceof PropertyValue) {
248 PropertyValue that = (PropertyValue)obj;
249 if (!this.getName().equals(that.getName())) return false;
250 return true;
251 }
252 return false;
253 }
254
255 /**
256 * {@inheritDoc}
257 */
258 @Override
259 public String toString() {
260 return "[" + this.name + "=" + value + "]";
261 }
262 }
263
264 /**
265 * An entry in a SequencerOutputMap, which contains the path of the node and the {@link #getPropertyValues() property values}
266 * on the node.
267 *
268 * @author Randall Hauch
269 */
270 @Immutable
271 public class Entry {
272
273 private final Path path;
274 private final Name primaryType;
275 private final List<PropertyValue> properties;
276
277 protected Entry( Path path,
278 List<PropertyValue> properties ) {
279 assert path != null;
280 assert properties != null;
281 this.path = path;
282 this.properties = properties;
283 if (this.properties.size() > 0 && this.properties.get(0).getName().equals("jcr:primaryType")) {
284 PropertyValue primaryTypeProperty = this.properties.remove(0);
285 this.primaryType = getFactories().getNameFactory().create(primaryTypeProperty.getValue());
286 } else {
287 this.primaryType = null;
288 }
289 }
290
291 /**
292 * @return path
293 */
294 public Path getPath() {
295 return this.path;
296 }
297
298 /**
299 * Get the primary type specified for this node, or null if the type was not specified
300 *
301 * @return the primary type, or null
302 */
303 public Name getPrimaryTypeValue() {
304 return this.primaryType;
305 }
306
307 /**
308 * Get the property values, which may be empty
309 *
310 * @return value
311 */
312 public List<PropertyValue> getPropertyValues() {
313 return getProperties(path);
314 }
315 }
316
317 protected class EntryIterator implements Iterator<Entry> {
318
319 private Path last;
320 private final Iterator<Path> iter;
321
322 protected EntryIterator( Iterator<Path> iter ) {
323 this.iter = iter;
324 }
325
326 /**
327 * {@inheritDoc}
328 */
329 public boolean hasNext() {
330 return iter.hasNext();
331 }
332
333 /**
334 * {@inheritDoc}
335 */
336 public Entry next() {
337 this.last = iter.next();
338 return new Entry(last, getProperties(last));
339 }
340
341 /**
342 * {@inheritDoc}
343 */
344 public void remove() {
345 if (last == null) throw new IllegalStateException();
346 try {
347 removeProperties(last);
348 } finally {
349 last = null;
350 }
351 }
352 }
353
354 }