View Javadoc
1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.creadur.whisker.scan;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.Collection;
25  import java.util.LinkedList;
26  import java.util.Queue;
27  import java.util.Set;
28  import java.util.TreeSet;
29  
30  /**
31   * Scans directories for resources, within a file system.
32   */
33  public class FromFileSystem {
34  
35      /**
36       * Base constructor.
37       */
38      public FromFileSystem() {
39          super();
40      }
41  
42      /**
43       * Builds description based on given directory.
44       * @param base names the base directory, not null
45       * @return collected directories within the base, not null
46       * @throws IOException when the scanning fails
47       */
48      public Collection<Directory> withBase(final String base)
49              throws IOException {
50          return new Builder(base).build();
51      }
52  
53      /**
54       * Builds a description of a file system.
55       */
56      private final static class Builder {
57          /** Initial capacity for the backing array. */
58          private static final int DEFAULT_INITIAL_CAPACITY = 64;
59          /** Directory scanning base. */
60          private final File base;
61          /** Directories scanned. */
62          private final Set<Directory> directories;
63          /** Queues work not yet complete. */
64          private final Queue<Work> workInProgress;
65          /** Stores work done. */
66          private final Collection<Work> workDone;
67  
68          /**
69           * Constructs a builder with given base
70           * (and default backing array).
71           * @param base not null
72           */
73          public Builder(final String base) {
74              this(base, DEFAULT_INITIAL_CAPACITY);
75          }
76  
77          /**
78           * Constructs a builder.
79           * @param base not null
80           * @param initialCapacity initial capacity for backing array
81           */
82          public Builder(final String base, final int initialCapacity) {
83              super();
84              this.base = new File(base);
85              directories = new TreeSet<Directory>();
86              workInProgress = new LinkedList<Work>();
87              workDone = new ArrayList<Work>(initialCapacity);
88          }
89  
90          /**
91           * Builds directories.
92           * @return not null
93           * @throws IOException when scanning fails
94           */
95          public Collection<Directory> build() throws IOException {
96              put(base).andWork().untilDone();
97              return directories;
98          }
99  
100         /**
101          * Waiting until work done.
102          */
103         private void untilDone() { }
104 
105         /**
106          * Adds file work to the queue.
107          * @param file not null
108          * @return this, not null
109          */
110         private Builder put(final File file) {
111             return put(new Work(file));
112         }
113 
114         /**
115          * Queues work.
116          * @param work not null
117          * @return this, not null
118          */
119         private Builder put(final Work work) {
120             if (work != null) {
121                 if (workDone.contains(work)) {
122                     alreadyDone(work);
123                 } else {
124                     this.workInProgress.add(work);
125                 }
126             }
127             return this;
128         }
129 
130         /**
131          * Notes that work has already been done.
132          * @param work not null
133          */
134         private void alreadyDone(final Work work) {
135             System.out.println("Already done " + work);
136         }
137 
138         /**
139          * Starts work.
140          * @return this, not null
141          */
142         private Builder andWork() {
143             while (!workInProgress.isEmpty()) {
144                 workDone.add(workOn(workInProgress.poll()));
145             }
146             return this;
147         }
148 
149         /**
150          * Performs work.
151          * @param next not null
152          * @return the work done, not null
153          */
154         private Work workOn(final Work next) {
155             for (final String name: next.contents()) {
156                 put(next.whenDirectory(name));
157             }
158             directories.add(next.build());
159             return next;
160         }
161 
162         /**
163          * Computes the contents of a directory.
164          */
165         private static final class Work {
166             /** Represents base directory. */
167             private static final String BASE_DIRECTORY = ".";
168             /** Names the directory. */
169             private final String name;
170             /** The directory worked on. */
171             private final File file;
172 
173             /**
174              * Constructs work.
175              * @param file not null
176              */
177             public Work(final File file) {
178                 this(BASE_DIRECTORY, file);
179             }
180 
181             /**
182              * Constructs work.
183              * @param name not null
184              * @param file not null
185              */
186             public Work(final String name, final File file) {
187                 if (!file.exists()) {
188                     throw new IllegalArgumentException(
189                             "Expected '" + file.getAbsolutePath() + "' to exist");
190                 }
191                 if (!file.isDirectory()) {
192                     throw new IllegalArgumentException(
193                             "Expected '" + file.getAbsolutePath() + "' to be a directory");
194                 }
195                 this.name = name;
196                 this.file = file;
197             }
198 
199             /**
200              * Gets the contents of the work directory.
201              * @return not null
202              */
203             public String[] contents() {
204                 final String[] contents = file.list();
205                 if (contents == null) {
206                     throw new IllegalArgumentException("Cannot list content of " + file);
207                 }
208                 return contents;
209             }
210 
211             /**
212              * Builds a directory.
213              * @return not null
214              */
215             public Directory build() {
216                 final Directory result = new Directory().setName(name);
217                 for (final String name : contents()) {
218                     if (isResource(name)) {
219                         result.addResource(name);
220                     }
221                 }
222                 return result;
223             }
224 
225             /**
226              * Is the named file a resource?
227              * @param name not null
228              * @return true when the named file is a resource,
229              * false otherwise
230              */
231             private boolean isResource(final String name) {
232                 return !isDirectory(name);
233             }
234 
235             /**
236              * Is the named file a directory?
237              * @param name not null
238              * @return true when the named file is a directory,
239              * false otherwise
240              */
241             private boolean isDirectory(final String name) {
242                 return file(name).isDirectory();
243             }
244 
245             /**
246              * Creates new work.
247              * @param name not null
248              * @return work for the named directory,
249              * or null when the resource named is not a directory
250              */
251             public Work whenDirectory(final String name) {
252                 final File file = file(name);
253                 final Work result;
254                 if (file.isDirectory()) {
255                     result = new Work(path(name), file);
256                 } else {
257                     result = null;
258                 }
259                 return result;
260             }
261 
262             /**
263              * Converts a name to a path relative to base.
264              * @param name not null
265              * @return not null
266              */
267             private String path(final String name) {
268                 final String result;
269                 if (isBaseDirectory()) {
270                     result = name;
271                 } else {
272                     result = this.name + "/" + name;
273                 }
274                 return result;
275             }
276 
277             /**
278              * This the work done in the base directory.
279              * @return true when this is the base, false otherwise.
280              */
281             private boolean isBaseDirectory() {
282                 return BASE_DIRECTORY.equals(this.name);
283             }
284 
285             /**
286              * Creates a file.
287              * @param name not null
288              * @return file with given name
289              */
290             private File file(String name) {
291                 return new File(this.file, name);
292             }
293 
294             /**
295              * Computes some suitable hash.
296              * @return a hash code
297              * @see java.lang.Object#hashCode()
298              */
299             @Override
300             public int hashCode() {
301                 final int prime = 31;
302                 int result = 1;
303                 result = prime * result
304                         + ((file == null) ? 0 : file.hashCode());
305                 result = prime * result
306                         + ((name == null) ? 0 : name.hashCode());
307                 return result;
308             }
309 
310             /**
311              * Equal when both name and file are equal.
312              * @param obj possibly null
313              * @return true when equal, false otherwise
314              * @see java.lang.Object#equals(java.lang.Object)
315              */
316             @Override
317             public boolean equals(final Object obj) {
318                 if (this == obj) {
319                     return true;
320                 }
321                 if (obj == null) {
322                     return false;
323                 }
324                 if (getClass() != obj.getClass()) {
325                     return false;
326                 }
327                 final Work other = (Work) obj;
328                 if (file == null) {
329                     if (other.file != null)
330                         return false;
331                 } else if (!file.equals(other.file))
332                     return false;
333                 if (name == null) {
334                     if (other.name != null) {
335                         return false;
336                     }
337                 } else if (!name.equals(other.name)) {
338                     return false;
339                 }
340                 return true;
341             }
342 
343             /**
344              * Something suitable for logging.
345              * @return not null
346              * @see java.lang.Object#toString()
347              */
348             @Override
349             public String toString() {
350                 return "Work [name=" + name + ", file=" + file + "]";
351             }
352         }
353     }
354 }