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