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 return other.name == null;
335 } else return name.equals(other.name);
336 }
337
338 /**
339 * Something suitable for logging.
340 * @return not null
341 * @see java.lang.Object#toString()
342 */
343 @Override
344 public String toString() {
345 return "Work [name=" + name + ", file=" + file + "]";
346 }
347 }
348 }
349 }