1//
2//  ========================================================================
3//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4//  ------------------------------------------------------------------------
5//  All rights reserved. This program and the accompanying materials
6//  are made available under the terms of the Eclipse Public License v1.0
7//  and Apache License v2.0 which accompanies this distribution.
8//
9//      The Eclipse Public License is available at
10//      http://www.eclipse.org/legal/epl-v10.html
11//
12//      The Apache License v2.0 is available at
13//      http://www.opensource.org/licenses/apache2.0.php
14//
15//  You may elect to redistribute this code under either of these licenses.
16//  ========================================================================
17//
18
19package org.eclipse.jetty.util.resource;
20
21import java.io.File;
22import java.io.IOException;
23import java.io.InputStream;
24import java.io.OutputStream;
25import java.net.MalformedURLException;
26import java.net.URL;
27import java.util.ArrayList;
28import java.util.Arrays;
29import java.util.HashSet;
30import java.util.List;
31import java.util.StringTokenizer;
32
33import org.eclipse.jetty.util.URIUtil;
34
35/**
36 * A collection of resources (dirs).
37 * Allows webapps to have multiple (static) sources.
38 * The first resource in the collection is the main resource.
39 * If a resource is not found in the main resource, it looks it up in
40 * the order the resources were constructed.
41 *
42 *
43 *
44 */
45public class ResourceCollection extends Resource
46{
47    private Resource[] _resources;
48
49    /* ------------------------------------------------------------ */
50    /**
51     * Instantiates an empty resource collection.
52     *
53     * This constructor is used when configuring jetty-maven-plugin.
54     */
55    public ResourceCollection()
56    {
57        _resources = new Resource[0];
58    }
59
60    /* ------------------------------------------------------------ */
61    /**
62     * Instantiates a new resource collection.
63     *
64     * @param resources the resources to be added to collection
65     */
66    public ResourceCollection(Resource... resources)
67    {
68        List<Resource> list = new ArrayList<Resource>();
69        for (Resource r : resources)
70        {
71            if (r==null)
72                continue;
73            if (r instanceof ResourceCollection)
74            {
75                for (Resource r2 : ((ResourceCollection)r).getResources())
76                    list.add(r2);
77            }
78            else
79                list.add(r);
80        }
81        _resources = list.toArray(new Resource[list.size()]);
82        for(Resource r : _resources)
83        {
84            if(!r.exists() || !r.isDirectory())
85                throw new IllegalArgumentException(r + " is not an existing directory.");
86        }
87    }
88
89
90    /* ------------------------------------------------------------ */
91    /**
92     * Instantiates a new resource collection.
93     *
94     * @param resources the resource strings to be added to collection
95     */
96    public ResourceCollection(String[] resources)
97    {
98        _resources = new Resource[resources.length];
99        try
100        {
101            for(int i=0; i<resources.length; i++)
102            {
103                _resources[i] = Resource.newResource(resources[i]);
104                if(!_resources[i].exists() || !_resources[i].isDirectory())
105                    throw new IllegalArgumentException(_resources[i] + " is not an existing directory.");
106            }
107        }
108        catch(IllegalArgumentException e)
109        {
110            throw e;
111        }
112        catch(Exception e)
113        {
114            throw new RuntimeException(e);
115        }
116    }
117
118    /* ------------------------------------------------------------ */
119    /**
120     * Instantiates a new resource collection.
121     *
122     * @param csvResources the string containing comma-separated resource strings
123     */
124    public ResourceCollection(String csvResources)
125    {
126        setResourcesAsCSV(csvResources);
127    }
128
129    /* ------------------------------------------------------------ */
130    /**
131     * Retrieves the resource collection's resources.
132     *
133     * @return the resource array
134     */
135    public Resource[] getResources()
136    {
137        return _resources;
138    }
139
140    /* ------------------------------------------------------------ */
141    /**
142     * Sets the resource collection's resources.
143     *
144     * @param resources the new resource array
145     */
146    public void setResources(Resource[] resources)
147    {
148        _resources = resources != null ? resources : new Resource[0];
149    }
150
151    /* ------------------------------------------------------------ */
152    /**
153     * Sets the resources as string of comma-separated values.
154     * This method should be used when configuring jetty-maven-plugin.
155     *
156     * @param csvResources the comma-separated string containing
157     *                     one or more resource strings.
158     */
159    public void setResourcesAsCSV(String csvResources)
160    {
161        StringTokenizer tokenizer = new StringTokenizer(csvResources, ",;");
162        int len = tokenizer.countTokens();
163        if(len==0)
164        {
165            throw new IllegalArgumentException("ResourceCollection@setResourcesAsCSV(String) " +
166                    " argument must be a string containing one or more comma-separated resource strings.");
167        }
168
169        _resources = new Resource[len];
170        try
171        {
172            for(int i=0; tokenizer.hasMoreTokens(); i++)
173            {
174                _resources[i] = Resource.newResource(tokenizer.nextToken().trim());
175                if(!_resources[i].exists() || !_resources[i].isDirectory())
176                    throw new IllegalArgumentException(_resources[i] + " is not an existing directory.");
177            }
178        }
179        catch(Exception e)
180        {
181            throw new RuntimeException(e);
182        }
183    }
184
185    /* ------------------------------------------------------------ */
186    /**
187     * @param path The path segment to add
188     * @return The contained resource (found first) in the collection of resources
189     */
190    @Override
191    public Resource addPath(String path) throws IOException, MalformedURLException
192    {
193        if(_resources==null)
194            throw new IllegalStateException("*resources* not set.");
195
196        if(path==null)
197            throw new MalformedURLException();
198
199        if(path.length()==0 || URIUtil.SLASH.equals(path))
200            return this;
201
202        Resource resource=null;
203        ArrayList<Resource> resources = null;
204        int i=0;
205        for(; i<_resources.length; i++)
206        {
207            resource = _resources[i].addPath(path);
208            if (resource.exists())
209            {
210                if (resource.isDirectory())
211                    break;
212                return resource;
213            }
214        }
215
216        for(i++; i<_resources.length; i++)
217        {
218            Resource r = _resources[i].addPath(path);
219            if (r.exists() && r.isDirectory())
220            {
221                if (resource!=null)
222                {
223                    resources = new ArrayList<Resource>();
224                    resources.add(resource);
225                    resource=null;
226                }
227                resources.add(r);
228            }
229        }
230
231        if (resource!=null)
232            return resource;
233        if (resources!=null)
234            return new ResourceCollection(resources.toArray(new Resource[resources.size()]));
235        return null;
236    }
237
238    /* ------------------------------------------------------------ */
239    /**
240     * @param path
241     * @return the resource(file) if found, returns a list of resource dirs if its a dir, else null.
242     * @throws IOException
243     * @throws MalformedURLException
244     */
245    protected Object findResource(String path) throws IOException, MalformedURLException
246    {
247        Resource resource=null;
248        ArrayList<Resource> resources = null;
249        int i=0;
250        for(; i<_resources.length; i++)
251        {
252            resource = _resources[i].addPath(path);
253            if (resource.exists())
254            {
255                if (resource.isDirectory())
256                    break;
257
258                return resource;
259            }
260        }
261
262        for(i++; i<_resources.length; i++)
263        {
264            Resource r = _resources[i].addPath(path);
265            if (r.exists() && r.isDirectory())
266            {
267                if (resource!=null)
268                {
269                    resources = new ArrayList<Resource>();
270                    resources.add(resource);
271                }
272                resources.add(r);
273            }
274        }
275
276        if (resource!=null)
277            return resource;
278        if (resources!=null)
279            return resources;
280        return null;
281    }
282
283    /* ------------------------------------------------------------ */
284    @Override
285    public boolean delete() throws SecurityException
286    {
287        throw new UnsupportedOperationException();
288    }
289
290    /* ------------------------------------------------------------ */
291    @Override
292    public boolean exists()
293    {
294        if(_resources==null)
295            throw new IllegalStateException("*resources* not set.");
296
297        return true;
298    }
299
300    /* ------------------------------------------------------------ */
301    @Override
302    public File getFile() throws IOException
303    {
304        if(_resources==null)
305            throw new IllegalStateException("*resources* not set.");
306
307        for(Resource r : _resources)
308        {
309            File f = r.getFile();
310            if(f!=null)
311                return f;
312        }
313        return null;
314    }
315
316    /* ------------------------------------------------------------ */
317    @Override
318    public InputStream getInputStream() throws IOException
319    {
320        if(_resources==null)
321            throw new IllegalStateException("*resources* not set.");
322
323        for(Resource r : _resources)
324        {
325            InputStream is = r.getInputStream();
326            if(is!=null)
327                return is;
328        }
329        return null;
330    }
331
332    /* ------------------------------------------------------------ */
333    @Override
334    public String getName()
335    {
336        if(_resources==null)
337            throw new IllegalStateException("*resources* not set.");
338
339        for(Resource r : _resources)
340        {
341            String name = r.getName();
342            if(name!=null)
343                return name;
344        }
345        return null;
346    }
347
348    /* ------------------------------------------------------------ */
349    @Override
350    public OutputStream getOutputStream() throws IOException, SecurityException
351    {
352        if(_resources==null)
353            throw new IllegalStateException("*resources* not set.");
354
355        for(Resource r : _resources)
356        {
357            OutputStream os = r.getOutputStream();
358            if(os!=null)
359                return os;
360        }
361        return null;
362    }
363
364    /* ------------------------------------------------------------ */
365    @Override
366    public URL getURL()
367    {
368        if(_resources==null)
369            throw new IllegalStateException("*resources* not set.");
370
371        for(Resource r : _resources)
372        {
373            URL url = r.getURL();
374            if(url!=null)
375                return url;
376        }
377        return null;
378    }
379
380    /* ------------------------------------------------------------ */
381    @Override
382    public boolean isDirectory()
383    {
384        if(_resources==null)
385            throw new IllegalStateException("*resources* not set.");
386
387        return true;
388    }
389
390    /* ------------------------------------------------------------ */
391    @Override
392    public long lastModified()
393    {
394        if(_resources==null)
395            throw new IllegalStateException("*resources* not set.");
396
397        for(Resource r : _resources)
398        {
399            long lm = r.lastModified();
400            if (lm!=-1)
401                return lm;
402        }
403        return -1;
404    }
405
406    /* ------------------------------------------------------------ */
407    @Override
408    public long length()
409    {
410        return -1;
411    }
412
413    /* ------------------------------------------------------------ */
414    /**
415     * @return The list of resource names(merged) contained in the collection of resources.
416     */
417    @Override
418    public String[] list()
419    {
420        if(_resources==null)
421            throw new IllegalStateException("*resources* not set.");
422
423        HashSet<String> set = new HashSet<String>();
424        for(Resource r : _resources)
425        {
426            for(String s : r.list())
427                set.add(s);
428        }
429        String[] result=set.toArray(new String[set.size()]);
430        Arrays.sort(result);
431        return result;
432    }
433
434    /* ------------------------------------------------------------ */
435    @Override
436    public void release()
437    {
438        if(_resources==null)
439            throw new IllegalStateException("*resources* not set.");
440
441        for(Resource r : _resources)
442            r.release();
443    }
444
445    /* ------------------------------------------------------------ */
446    @Override
447    public boolean renameTo(Resource dest) throws SecurityException
448    {
449        throw new UnsupportedOperationException();
450    }
451
452    /* ------------------------------------------------------------ */
453    @Override
454    public void copyTo(File destination)
455        throws IOException
456    {
457        for (int r=_resources.length;r-->0;)
458            _resources[r].copyTo(destination);
459    }
460
461    /* ------------------------------------------------------------ */
462    /**
463     * @return the list of resources separated by a path separator
464     */
465    @Override
466    public String toString()
467    {
468        if(_resources==null)
469            return "[]";
470
471        return String.valueOf(Arrays.asList(_resources));
472    }
473
474    /* ------------------------------------------------------------ */
475    @Override
476    public boolean isContainedIn(Resource r) throws MalformedURLException
477    {
478        // TODO could look at implementing the semantic of is this collection a subset of the Resource r?
479        return false;
480    }
481
482}
483