001package org.jszip.jetty;
002
003import org.apache.commons.lang3.StringUtils;
004import org.eclipse.jetty.util.URIUtil;
005import org.eclipse.jetty.util.resource.Resource;
006
007import java.io.File;
008import java.io.IOException;
009import java.io.InputStream;
010import java.io.OutputStream;
011import java.net.MalformedURLException;
012import java.net.URL;
013import java.net.URLConnection;
014import java.net.URLStreamHandler;
015
016/**
017 * In order to support the {@link org.jszip.maven.Mapping} we need virtual resources to handle the path offset.
018 */
019public class VirtualDirectoryResource extends Resource {
020
021    /**
022     * The resource that we are inserting a virtual path in front of.
023     */
024    private Resource child;
025
026    /**
027     * The name of the child resource
028     */
029    private final String name;
030
031    /**
032     * The url of the child resource.
033     */
034    private final String pathName;
035
036    /**
037     * Creates a tree of virtual resources in order to present the child resource at the provided path.
038     *
039     * @param child     the child resource.
040     * @param childPath the path at which the child resource will appear.
041     */
042    public VirtualDirectoryResource(Resource child, String childPath) {
043        this(child, "/", childPath);
044    }
045
046    private VirtualDirectoryResource(Resource child, String path, String name) {
047        int index = name.indexOf(URIUtil.SLASH);
048        if (index == 0) {
049            name = name.substring(1);
050            index = name.indexOf(URIUtil.SLASH);
051        }
052        path = StringUtils.strip(path, "/");
053        if (StringUtils.isBlank(path)) {
054            this.pathName = "/";
055        } else {
056            this.pathName  = "/" + path + "/";
057        }
058        if ((index == -1) || (index == name.length() - 1)) {
059            this.child = child;
060            this.name = name;
061        } else {
062            this.name = name.substring(0, index);
063            this.child = new VirtualDirectoryResource(child, pathName + this.name + "/", name.substring(index + 1));
064        }
065    }
066
067    /**
068     * Returns our child resource.
069     * @return our child resource.
070     */
071    public Resource getChild() {
072        return child;
073    }
074
075    /**
076     * Sets our child resource.
077     * @param child our child resource.
078     */
079    public void setChild(Resource child) {
080        this.child = child;
081    }
082
083    /** {@inheritDoc} */
084    @Override
085    public boolean isContainedIn(Resource resource) throws MalformedURLException {
086        return false;
087    }
088
089    /** {@inheritDoc} */
090    @Override
091    public void release() {
092        if (child != null) {
093            child.release();
094        }
095    }
096
097    /** {@inheritDoc} */
098    @Override
099    public boolean exists() {
100        return true;
101    }
102
103    /** {@inheritDoc} */
104    @Override
105    public boolean isDirectory() {
106        return true;
107    }
108
109    /** {@inheritDoc} */
110    @Override
111    public long lastModified() {
112        return child != null ? child.lastModified() : -1;
113    }
114
115    /** {@inheritDoc} */
116    @Override
117    public long length() {
118        return -1;
119    }
120
121    /** {@inheritDoc} */
122    @Override
123    public URL getURL() {
124        try {
125            return new URL("virtual", null, -1, pathName, VirtualURLStreamHandler.INSTANCE);
126        } catch (MalformedURLException e) {
127            throw new IllegalStateException(
128                    "MalformedURLException should not be thrown when a URLStreamHandler is provided");
129        }
130    }
131
132    /** {@inheritDoc} */
133    @Override
134    public File getFile() throws IOException {
135        return null;
136    }
137
138    /** {@inheritDoc} */
139    @Override
140    public String getName() {
141        return null;
142    }
143
144    /** {@inheritDoc} */
145    @Override
146    public InputStream getInputStream() throws IOException {
147        return null;
148    }
149
150    /** {@inheritDoc} */
151    @Override
152    public OutputStream getOutputStream() throws IOException, SecurityException {
153        return null;
154    }
155
156    /** {@inheritDoc} */
157    @Override
158    public boolean delete() throws SecurityException {
159        throw new UnsupportedOperationException();
160    }
161
162    /** {@inheritDoc} */
163    @Override
164    public boolean renameTo(Resource resource) throws SecurityException {
165        throw new UnsupportedOperationException();
166    }
167
168    /** {@inheritDoc} */
169    @Override
170    public String[] list() {
171        return new String[]{child.isDirectory() ? name + "/" : name};
172    }
173
174    /** {@inheritDoc} */
175    @Override
176    public Resource addPath(String path) throws IOException, MalformedURLException {
177        if (path == null) {
178            throw new MalformedURLException();
179        }
180        if (path.length() == 0 || URIUtil.SLASH.equals(path)) {
181            return this;
182        }
183        if (path.startsWith(name) || path.startsWith(URIUtil.SLASH + name)) {
184            return child.addPath(path.substring(path.indexOf(name) + name.length()));
185        }
186        return new BadResource();
187    }
188
189    /** {@inheritDoc} */
190    @Override
191    public String toString() {
192        final StringBuilder sb = new StringBuilder();
193        sb.append("VirtualDirectoryResource");
194        sb.append("{url='virtual:").append(pathName).append('\'');
195        sb.append(", name='").append(name).append('\'');
196        sb.append(", child=").append(child);
197        sb.append('}');
198        return sb.toString();
199    }
200
201    /** {@inheritDoc} */
202    @Override
203    public boolean equals(Object o) {
204        if (this == o) {
205            return true;
206        }
207        if (!(o instanceof VirtualDirectoryResource)) {
208            return false;
209        }
210
211        VirtualDirectoryResource that = (VirtualDirectoryResource) o;
212
213        if (child != null ? !child.equals(that.child) : that.child != null) {
214            return false;
215        }
216        if (name != null ? !name.equals(that.name) : that.name != null) {
217            return false;
218        }
219
220        return true;
221    }
222
223    /** {@inheritDoc} */
224    @Override
225    public int hashCode() {
226        int result = child != null ? child.hashCode() : 0;
227        result = 31 * result + (name != null ? name.hashCode() : 0);
228        return result;
229    }
230
231    /**
232     * In order to ensure that we can create URLs with the {@code virtual:} protocol, we need to provide a dummy
233     * {@link URLStreamHandler} otherwise Java will try to look up the protocol and fail thereby throwing the
234     * dreaded {@link MalformedURLException}.
235     */
236    private static class VirtualURLStreamHandler extends URLStreamHandler {
237        /**
238         * The singleton instance.
239         */
240        private static final VirtualURLStreamHandler INSTANCE = new VirtualURLStreamHandler();
241
242        /**
243         * {@inheritDoc}
244         */
245        @Override
246        protected URLConnection openConnection(URL u) throws IOException {
247            throw new IOException("virtual:" + u.getPath() + " is a virtual URL");
248        }
249    }
250
251}