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 }