001/*
002 * ModeShape (http://www.modeshape.org)
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *       http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.modeshape.schematic.internal.document;
017
018import java.util.ArrayList;
019import java.util.Arrays;
020import java.util.Iterator;
021import java.util.List;
022import java.util.NoSuchElementException;
023import org.modeshape.schematic.annotation.Immutable;
024import org.modeshape.schematic.document.Path;
025
026public class Paths {
027
028    protected static final Path EMPTY_PATH = new EmptyPath();
029
030    protected static final void notNull( Object value,
031                                         String name ) {
032        if (value == null) throw new IllegalArgumentException("The '" + name + "' argument may not be null");
033    }
034
035    protected static final String notNull( String value,
036                                           String name ) {
037        if (value == null) throw new IllegalArgumentException("The '" + name + "' argument may not be null");
038        value = value.trim();
039        return value;
040    }
041
042    public static Path rootPath() {
043        return EMPTY_PATH;
044    }
045
046    public static Path path( String fieldName ) {
047        return new SinglePath(notNull(fieldName, "fieldName"));
048    }
049
050    public static Path path( String... fieldNames ) {
051        notNull(fieldNames, "fieldNames");
052        switch (fieldNames.length) {
053            case 0:
054                return EMPTY_PATH;
055            case 1:
056                return new SinglePath(fieldNames[0]);
057            default:
058                return new MultiSegmentPath(Arrays.asList(fieldNames));
059        }
060    }
061
062    public static Path path( List<String> fieldNames ) {
063        notNull(fieldNames, "fieldNames");
064        switch (fieldNames.size()) {
065            case 0:
066                return EMPTY_PATH;
067            case 1:
068                return new SinglePath(fieldNames.get(0));
069            default:
070                // make copy so we're guaranteed to be immutable
071                return new MultiSegmentPath(new ArrayList<>(fieldNames));
072        }
073    }
074
075    public static Path path( Path path,
076                             String fieldName ) {
077        notNull(path, "path");
078        return path.with(notNull(fieldName, "fieldName"));
079    }
080
081    public static Path path( Path path,
082                             String... fieldNames ) {
083        notNull(path, "path");
084        notNull(fieldNames, "fieldNames");
085        if (fieldNames.length == 0) return path;
086        ArrayList<String> names = new ArrayList<>(path.size() + fieldNames.length);
087        int i = 0;
088        for (String name : path) {
089            names.add(notNull(name, "fieldNames[" + i++ + "]"));
090        }
091        for (String name : fieldNames) {
092            names.add(notNull(name, "fieldNames[" + i++ + "]"));
093        }
094        return new MultiSegmentPath(names);
095    }
096
097    public static Path path( Path path,
098                             List<String> fieldNames ) {
099        notNull(path, "path");
100        notNull(fieldNames, "fieldNames");
101        if (fieldNames.isEmpty()) return path;
102        ArrayList<String> names = new ArrayList<>(path.size() + fieldNames.size());
103        int i = 0;
104        for (String name : path) {
105            names.add(notNull(name, "fieldNames[" + i++ + "]"));
106        }
107        for (String name : fieldNames) {
108            names.add(notNull(name, "fieldNames[" + i++ + "]"));
109        }
110        return new MultiSegmentPath(names);
111    }
112
113    @Immutable
114    protected static final class EmptyPath implements Path {
115        @Override
116        public Iterator<String> iterator() {
117            return new Iterator<String>() {
118                @Override
119                public boolean hasNext() {
120                    return false;
121                }
122
123                @Override
124                public String next() {
125                    throw new NoSuchElementException();
126                }
127
128                @Override
129                public void remove() {
130                    throw new UnsupportedOperationException();
131                }
132            };
133        }
134
135        @Override
136        public String get( int index ) {
137            throw new IndexOutOfBoundsException("Index: " + index + ", Size: 0");
138        }
139
140        @Override
141        public String getLast() {
142            return null;
143        }
144
145        @Override
146        public String getFirst() {
147            return null;
148        }
149
150        @Override
151        public int size() {
152            return 0;
153        }
154
155        @Override
156        public boolean startsWith( Path ancestor ) {
157            return ancestor.size() == 0;
158        }
159
160        @Override
161        public Path with( String fieldName ) {
162            return fieldName != null ? new SinglePath(notNull(fieldName, "fieldName")) : this;
163        }
164
165        @Override
166        public Path parent() {
167            return EMPTY_PATH;
168        }
169
170        @Override
171        public int compareTo( Path that ) {
172            if (that == this) return 0;
173            if (that instanceof EmptyPath) return 0;
174            return 0 - that.size();
175        }
176
177        @Override
178        public int hashCode() {
179            return 1;
180        }
181
182        @Override
183        public boolean equals( Object obj ) {
184            if (obj == this) return true;
185            if (obj instanceof Path) {
186                Path that = (Path)obj;
187                return that.size() == 0;
188            }
189            return false;
190        }
191
192        @Override
193        public String toString() {
194            return "";
195        }
196    }
197
198    @Immutable
199    protected static final class SinglePath implements Path {
200
201        private final String fieldName;
202
203        protected SinglePath( String fieldName ) {
204            this.fieldName = fieldName;
205        }
206
207        @Override
208        public Iterator<String> iterator() {
209            return new Iterator<String>() {
210                private boolean done = false;
211
212                @Override
213                public boolean hasNext() {
214                    return !done;
215                }
216
217                @SuppressWarnings( "synthetic-access" )
218                @Override
219                public String next() {
220                    done = true;
221                    return fieldName;
222                }
223
224                @Override
225                public void remove() {
226                    throw new UnsupportedOperationException();
227                }
228            };
229        }
230
231        @Override
232        public String get( int index ) {
233            if (index != 0) throw new IndexOutOfBoundsException("Index: " + index + ", Size: 1");
234            return fieldName;
235        }
236
237        @Override
238        public String getLast() {
239            return fieldName;
240        }
241
242        @Override
243        public String getFirst() {
244            return fieldName;
245        }
246
247        @Override
248        public Path with( String fieldName ) {
249            return Paths.path(this.fieldName, fieldName);
250        }
251
252        @Override
253        public Path parent() {
254            return EMPTY_PATH;
255        }
256
257        @Override
258        public int size() {
259            return 1;
260        }
261
262        @Override
263        public int hashCode() {
264            return fieldName.hashCode();
265        }
266
267        @Override
268        public int compareTo( Path that ) {
269            if (that == this) return 0;
270            int diff = this.size() - that.size();
271            if (diff != 0) return diff;
272            return this.fieldName.compareTo(that.get(0));
273        }
274
275        @Override
276        public boolean startsWith( Path ancestor ) {
277            return this.equals(ancestor);
278        }
279
280        @Override
281        public boolean equals( Object obj ) {
282            if (obj == this) return true;
283            if (obj instanceof SinglePath) {
284                SinglePath that = (SinglePath)obj;
285                return this.fieldName.equals(that.fieldName);
286            }
287            if (obj instanceof Path) {
288                Path that = (Path)obj;
289                if (this.size() != that.size()) return false;
290                return this.fieldName.equals(that.get(0));
291            }
292            return false;
293        }
294
295        @Override
296        public String toString() {
297            return fieldName;
298        }
299    }
300
301    @Immutable 
302    protected static final class MultiSegmentPath implements Path {
303
304        private final List<String> fieldNames;
305        private transient String composite;
306
307        protected MultiSegmentPath( List<String> fieldNames ) {
308            assert fieldNames != null;
309            assert !fieldNames.isEmpty();
310            this.fieldNames = fieldNames;
311        }
312
313        @Override
314        public Iterator<String> iterator() {
315            final Iterator<String> actualIter = fieldNames.iterator();
316            return new Iterator<String>() {
317                @Override
318                public boolean hasNext() {
319                    return actualIter.hasNext();
320                }
321
322                @Override
323                public String next() {
324                    return actualIter.next();
325                }
326
327                @Override
328                public void remove() {
329                    throw new UnsupportedOperationException();
330                }
331            };
332        }
333
334        @Override
335        public String get( int index ) {
336            return fieldNames.get(index);
337        }
338
339        @Override
340        public String getLast() {
341            return fieldNames.get(fieldNames.size() - 1);
342        }
343
344        @Override
345        public String getFirst() {
346            return fieldNames.get(0);
347        }
348
349        @Override
350        public int size() {
351            return fieldNames.size();
352        }
353
354        @Override
355        public boolean startsWith( Path other ) {
356            if (other.size() > this.size()) return false;
357            Iterator<String> thatIter = other.iterator();
358            Iterator<String> thisIter = this.iterator();
359            while (thatIter.hasNext() && thisIter.hasNext()) {
360                if (!thisIter.next().equals(thatIter.next())) return false;
361            }
362            return !thatIter.hasNext();
363        }
364
365        @Override
366        public Path with( String fieldName ) {
367            fieldName = notNull(fieldName, "fieldName");
368            List<String> newFieldNames = new ArrayList<>(fieldNames.size() + 1);
369            newFieldNames.addAll(this.fieldNames);
370            newFieldNames.add(fieldName);
371            return new MultiSegmentPath(newFieldNames);
372        }
373
374        @Override
375        public Path parent() {
376            if (size() <= 1) return EMPTY_PATH;
377            List<String> parentFieldNames = this.fieldNames.subList(0, this.fieldNames.size() - 1);
378            return new MultiSegmentPath(parentFieldNames);
379        }
380
381        @Override
382        public int hashCode() {
383            return fieldNames.hashCode();
384        }
385
386        @Override
387        public int compareTo( Path that ) {
388            if (that == this) return 0;
389            int diff = this.size() - that.size();
390            if (diff != 0) return diff;
391            Iterator<String> thatIter = that.iterator();
392            Iterator<String> thisIter = this.iterator();
393            while (thatIter.hasNext()) {
394                int value = thisIter.next().compareTo(thatIter.next());
395                if (value != 0) return value;
396            }
397            assert !thisIter.hasNext();
398            return 0;
399        }
400
401        @Override
402        public boolean equals( Object obj ) {
403            if (obj == this) return true;
404            if (obj instanceof Path) {
405                Path that = (Path)obj;
406                if (this.size() != that.size()) return false;
407                Iterator<String> thatIter = that.iterator();
408                Iterator<String> thisIter = this.iterator();
409                while (thatIter.hasNext()) {
410                    if (!thisIter.next().equals(thatIter.next())) return false;
411                }
412                assert !thisIter.hasNext();
413                return true;
414            }
415            return false;
416        }
417
418        @Override
419        public String toString() {
420            if (composite == null) {
421                StringBuilder sb = new StringBuilder();
422                Iterator<String> iter = fieldNames.iterator();
423                if (iter.hasNext()) {
424                    sb.append(iter.next());
425                    while (iter.hasNext()) {
426                        sb.append('.');
427                        sb.append(iter.next());
428                    }
429                }
430                composite = sb.toString();
431            }
432            return composite;
433        }
434    }
435}