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}