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 }