001/* Copyright (C) 2013 TU Dortmund 002 * This file is part of AutomataLib, http://www.automatalib.net/. 003 * 004 * AutomataLib is free software; you can redistribute it and/or 005 * modify it under the terms of the GNU Lesser General Public 006 * License version 3.0 as published by the Free Software Foundation. 007 * 008 * AutomataLib is distributed in the hope that it will be useful, 009 * but WITHOUT ANY WARRANTY; without even the implied warranty of 010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 011 * Lesser General Public License for more details. 012 * 013 * You should have received a copy of the GNU Lesser General Public 014 * License along with AutomataLib; if not, see 015 * http://www.gnu.de/documents/lgpl.en.html. 016 */ 017package net.automatalib.words; 018 019import java.util.AbstractList; 020import java.util.List; 021 022import net.automatalib.commons.util.array.ResizingObjectArray; 023 024/** 025 * A class for dynamically building {@link Word}s. 026 * 027 * As {@link Word}s are - like strings - immutable objects, constructing them by subsequent 028 * invocations of {@link Word#concat(Word...)} etc. is highly inefficient. This class provides an 029 * efficient means of construction by operating on an internal storage during construction, 030 * only creating a {@link Word} (and thus requiring to ensure immutability) when the method {@link #toWord()} 031 * (or {@link #toWord(int, int)} is invoked. 032 * 033 * Note that due to the specifics of the underlying word implementation, even after an invocation 034 * of {@link #toWord()} the storage does not have to be duplicated unless it either is required 035 * due to capacity adjustment <i>or</i> a non-appending change (such as {@link #setSymbol(int, Object)} 036 * or {@link #truncate(int)}) is made. 037 * 038 * Nearly all modification methods of this class return a <tt>this</tt>-reference, allowing constructs 039 * such as 040 * <pre>builder.append(foo).append(bar).append(baz);</pre> 041 * 042 * @author Malte Isberner <malte.isberner@gmail.com> 043 * 044 * @param <I> symbol class. 045 */ 046public final class WordBuilder<I> extends AbstractList<I> { 047 048 private final ResizingObjectArray storage; 049 private Object[] array; 050 private int length; 051 private boolean lock = false; 052 053 /** 054 * Constructor. Initializes the builder with a default capacity. 055 */ 056 public WordBuilder() { 057 this.storage = new ResizingObjectArray(); 058 this.array = this.storage.array; 059 } 060 061 /** 062 * Constructor. Initializes the builder with the specified initial capacity. 063 * @param initialCapacity the initial capacity of the internal storage. 064 */ 065 public WordBuilder(int initialCapacity) { 066 this.storage = new ResizingObjectArray(initialCapacity); 067 this.array = this.storage.array; 068 } 069 070 /** 071 * Constructor. Initializes the builder with a sequence of <tt>count</tt> 072 * times the specified symbol. Note that this constructor runs in constant time 073 * if <tt>initSym</tt> is <tt>null</tt>. 074 * 075 * @param initSym the initial symbol 076 * @param count the initial symbol count 077 */ 078 public WordBuilder(I initSym, int count) { 079 this.storage = new ResizingObjectArray(count); 080 this.array = this.storage.array; 081 if(initSym != null) { 082 for(int i = 0; i < count; i++) 083 array[i] = initSym; 084 } 085 length = count; 086 } 087 088 /** 089 * Constructor. Initializes the builder with a sequence of <tt>count</tt> 090 * times the specified symbol, while allocating the specified initial capacity. 091 * @param capacity the initial capacity of the internal storage. 092 * @param initSym the initial symbol 093 * @param count the initial symbol count 094 */ 095 public WordBuilder(int capacity, I initSym, int count) { 096 if(capacity < count) 097 capacity = count; 098 this.storage = new ResizingObjectArray(capacity); 099 this.array = this.storage.array; 100 if(initSym != null) { 101 for(int i = 0; i < count; i++) 102 array[i] = initSym; 103 } 104 length = count; 105 } 106 107 /** 108 * Constructor. Initializes the builder with a given word. 109 * @param init the word to initialize the builder with. 110 */ 111 public WordBuilder(Word<I> init) { 112 int wLen = init.length(); 113 this.storage = new ResizingObjectArray(wLen); 114 this.array = this.storage.array; 115 init.writeToArray(0, array, 0, wLen); 116 length = wLen; 117 } 118 119 /** 120 * Constructor. Initializes the builder with a given word, while allocating 121 * the specified initial capacity. 122 * @param capacity the initial capacity to use. 123 * @param init the initial word 124 */ 125 public WordBuilder(int capacity, Word<I> init) { 126 int wLen = init.length(); 127 if(capacity < wLen) 128 capacity = wLen; 129 this.storage = new ResizingObjectArray(capacity); 130 this.array = this.storage.array; 131 init.writeToArray(0, array, 0, wLen); 132 length = wLen; 133 } 134 135 public WordBuilder<I> append(List<? extends I> symList) { 136 int lLen = symList.size(); 137 ensureAdditionalCapacity(lLen); 138 for(I sym : symList) 139 array[length++] = sym; 140 return this; 141 } 142 143 144 /** 145 * Appends a word to the contents of the internal storage. 146 * @param word the word to append. 147 * @return <tt>this</tt> 148 */ 149 public WordBuilder<I> append(Word<? extends I> word) { 150 int wLen = word.length(); 151 ensureAdditionalCapacity(wLen); 152 word.writeToArray(0, array, length, wLen); 153 length += wLen; 154 return this; 155 } 156 157 /** 158 * Appends several words to the contents of the internal storage. 159 * @param words the words to append 160 * @return <tt>this</tt> 161 */ 162 @SafeVarargs 163 public final WordBuilder<I> append(Word<? extends I> ...words) { 164 if(words.length == 0) 165 return this; 166 167 int allLen = 0; 168 for(int i = 0; i < words.length; i++) 169 allLen += words[i].length(); 170 171 ensureAdditionalCapacity(allLen); 172 173 for(int i = 0; i < words.length; i++) { 174 Word<? extends I> word = words[i]; 175 int wLen = word.length(); 176 word.writeToArray(0, array, length, wLen); 177 length += wLen; 178 } 179 180 return this; 181 } 182 183 /** 184 * Appends <tt>num</tt> copies of the given word to the contents 185 * of the initial storage. 186 * @param num the number of copies 187 * @param word the word 188 * @return <tt>this</tt> 189 */ 190 public WordBuilder<I> repeatAppend(int num, Word<I> word) { 191 if(num == 0) 192 return this; 193 194 int wLen = word.length(); 195 int allLen = wLen * num; 196 197 ensureAdditionalCapacity(allLen); 198 199 while(num-- > 0) { 200 word.writeToArray(0, array, length, wLen); 201 length += wLen; 202 } 203 204 return this; 205 } 206 207 /** 208 * Appends a symbol to the contents of the internal storage. 209 * @param symbol the symbol to append 210 * @return <tt>this</tt> 211 */ 212 public WordBuilder<I> append(I symbol) { 213 ensureAdditionalCapacity(1); 214 array[length++] = symbol; 215 return this; 216 } 217 218 /** 219 * Appends <tt>num</tt> copies of a symbol to the contents of the 220 * internal storage. 221 * @param num the number of copies 222 * @param symbol the symbol 223 * @return <tt>this</tt> 224 */ 225 public WordBuilder<I> repeatAppend(int num, I symbol) { 226 if(num == 0) 227 return this; 228 229 ensureAdditionalCapacity(num); 230 if(symbol == null) 231 length += num; 232 else { 233 while(num-- > 0) 234 array[length++] = symbol; 235 } 236 return this; 237 } 238 239 /** 240 * Appends several symbols to the contents of the internal storage. 241 * @param symbols the symbols to append 242 * @return <tt>this</tt> 243 */ 244 @SafeVarargs 245 public final WordBuilder<I> append(I ...symbols) { 246 if(symbols.length == 0) 247 return this; 248 ensureAdditionalCapacity(symbols.length); 249 System.arraycopy(symbols, 0, array, length, symbols.length); 250 length += symbols.length; 251 return this; 252 } 253 254 /** 255 * Ensures that the internal storage has in total the given capacity 256 * @param cap the minimum capacity to ensure 257 */ 258 public void ensureCapacity(int cap) { 259 if(storage.ensureCapacity(cap)) { 260 lock = false; 261 array = storage.array; 262 } 263 } 264 265 /** 266 * Ensures that the internal storage has <b>additionally</b> the given 267 * capacity. 268 * @param add the additional capacity to ensure 269 */ 270 public void ensureAdditionalCapacity(int add) { 271 ensureCapacity(length + add); 272 } 273 274 /* 275 * Ensure that non-appending modifications may be made 276 */ 277 private final void ensureUnlocked() { 278 if(lock) { 279 array = array.clone(); 280 storage.array = array; 281 lock = false; 282 } 283 } 284 285 /** 286 * Retrieves the symbol at the given index 287 * @param index the index to retrieve 288 * @return the symbol at the given index 289 */ 290 @SuppressWarnings("unchecked") 291 public I getSymbol(int index) { 292 return (I)array[index]; 293 } 294 295 /** 296 * Sets the symbol at the given index. Note that this index must exist. 297 * @param index the index to manipulate 298 * @param symbol the symbol to set 299 * @return <tt>this</tt> 300 */ 301 public WordBuilder<I> setSymbol(int index, I symbol) { 302 ensureUnlocked(); 303 array[index] = symbol; 304 return this; 305 } 306 307 /** 308 * Truncates the contents of the initial storage to the given length. 309 * @param truncLen the length to truncate to 310 * @return <tt>this</tt> 311 */ 312 public WordBuilder<I> truncate(int truncLen) { 313 if(truncLen >= length) 314 return this; 315 316 ensureUnlocked(); 317 for(int i = truncLen; i < length; i++) 318 array[i] = null; 319 320 length = truncLen; 321 322 return this; 323 } 324 325 /** 326 * Creates a word from the given range of the contents of the internal storage. 327 * Note that the storage management mechanisms of this class guarantee that 328 * the returned word will not change regardless of what further operations are invoked 329 * on this {@link WordBuilder}. 330 * @param fromIndex the starting index 331 * @param toIndex the end index 332 * @return the word for the specified subrange 333 */ 334 public Word<I> toWord(int fromIndex, int toIndex) { 335 if(fromIndex < 0 || toIndex > length) 336 throw new IndexOutOfBoundsException(); 337 int len = toIndex - fromIndex; 338 339 lock = true; 340 return new SharedWord<>(array, fromIndex, len); 341 } 342 343 /** 344 * Creates a word from the contents of the internal storage. 345 * Note that the storage management mechanisms of this class guarantee that 346 * the returned word will not change regardless of what further operations are performed 347 * on this {@link WordBuilder}. 348 * @return the internal contents as a word 349 */ 350 public Word<I> toWord() { 351 lock = true; 352 return new SharedWord<>(array, 0, length); 353 } 354 355 /* 356 * (non-Javadoc) 357 * @see java.util.AbstractList#add(java.lang.Object) 358 */ 359 @Override 360 public boolean add(I e) { 361 append(e); 362 return true; 363 } 364 365 /* 366 * (non-Javadoc) 367 * @see java.util.AbstractList#get(int) 368 */ 369 @Override 370 public I get(int index) { 371 return getSymbol(index); 372 } 373 374 /* 375 * (non-Javadoc) 376 * @see java.util.AbstractList#set(int, java.lang.Object) 377 */ 378 @Override 379 public I set(int index, I element) { 380 I old = getSymbol(index); 381 setSymbol(index, element); 382 return old; 383 } 384 385 /* 386 * (non-Javadoc) 387 * @see java.util.AbstractList#clear() 388 */ 389 @Override 390 public void clear() { 391 ensureUnlocked(); 392 for(int i = 0; i < length; i++) 393 array[i] = null; 394 length = 0; 395 } 396 397 /* 398 * (non-Javadoc) 399 * @see java.util.AbstractCollection#size() 400 */ 401 @Override 402 public int size() { 403 return length; 404 } 405 406 public WordBuilder<I> reverse() { 407 ensureUnlocked(); 408 int lowIdx = 0, highIdx = length - 1; 409 410 while(lowIdx < highIdx) { 411 Object tmp = array[lowIdx]; 412 array[lowIdx++] = array[highIdx]; 413 array[highIdx--] = tmp; 414 } 415 416 return this; 417 } 418 419 420}