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 }