public abstract class HTTPMessage
{
    public HTTPMessage(
            String httpVersion,
            Map<String, String> headers,
            byte[] content)
    {
        this.httpVersion = httpVersion;
        this.headers = headers;
        this.content = content;
    }
    
    protected static String formatHeaderName(String name)
    {
        return name.trim().replace(' ', '-').toLowerCase();
    }
    protected static String formatHeaderRaw(String header, String value)
    {
        return header + ": " + value;
    }
    
    // <editor-fold defaultstate="collapsed" desc="Parser Helper">
    private static int parserHelper_getHeaderSize(byte[] byteMessage)
    {
        int headerLen = 0;
        while(byteMessage[headerLen] != '\r'
                || byteMessage[headerLen + 1] != '\n'
                || byteMessage[headerLen + 2] != '\r'
                || byteMessage[headerLen + 3] != '\n')
        {
            headerLen++;
        }
        return headerLen;
    }
    private static String[] parserHelper_getHeadLineValues(String headLine)
    {
        int secondIndex = headLine.indexOf(" ");
        int thirdIndex = headLine.indexOf(" ", secondIndex + 1);
        
        return new String[]
        {
            headLine.substring(0, secondIndex),
            headLine.substring(secondIndex + 1, thirdIndex),
            headLine.substring(thirdIndex + 1)
        };
    }
    
    private static void parserHelper_parseHeaders(String messageWithoutHeadLine, BiConsumer<String, String> consumer)
    {
        Stream.of(messageWithoutHeadLine.split("\r\n"))
                .filter(s -> !s.isEmpty())
                .forEach(header ->
                {
                    final int sep = header.indexOf(':');
                    consumer.accept(header.substring(0, sep).trim(), header.substring(sep + 1).trim());
                });
    }
    private static void parserHelper_parseContent(byte[] byteMessage, int headerLen, Consumer<byte[]> consumer)
    {
        String separator = "\r\n\r\n";
        
        if(headerLen + separator.length() < byteMessage.length)
            consumer.accept(Arrays.copyOfRange(byteMessage, headerLen + separator.length(), byteMessage.length));
    }
    
    private static String parserHelper_getStringHeader(byte[] byteMessage, int headerLen)
    {
        try
        {
            return new String(byteMessage, 0, headerLen, "UTF-8");
        }
        catch (UnsupportedEncodingException ex)
        {
            return null;
        }
    }
    
    protected static void parseHTTPMessage(byte[] byteMessage, Consumer<String[]> headLineConsumer, BiConsumer<String, String> headerConsumer, Consumer<byte[]> contentConsumer)
    {
        if(byteMessage.length == 0)
            return;
        
        int headerLen = parserHelper_getHeaderSize(byteMessage);
        
        String message = parserHelper_getStringHeader(byteMessage, headerLen);
        
        int header_index = message.indexOf("\r\n");
        
        String[] head = parserHelper_getHeadLineValues(message.substring(0, header_index));
        headLineConsumer.accept(head);
        
        parserHelper_parseContent(byteMessage, headerLen, contentConsumer);
        parserHelper_parseHeaders(message.substring(header_index + 1), headerConsumer);
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Properties">
    protected final String httpVersion;
    protected final Map<String, String> headers;
    protected final byte[] content;
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc="Accessors">
    public String getHTTPVersion()
    {
        return httpVersion;
    }
    
    public Map<String, String> getHeaders()
    {
        return headers;
    }
    public String getHeader(String name)
    {
        return headers.get(formatHeaderName(name));
    }
    public String getHeader(String name, String defaultValue)
    {
        return headers.getOrDefault(formatHeaderName(name), defaultValue);
    }
    public boolean containsHeader(String name)
    {
        return headers.containsKey(formatHeaderName(name));
    }
    public String getHeadersString()
    {
        return headers.entrySet()
                .stream()
                .map(e -> formatHeaderRaw(e.getKey(), e.getValue()) + "\r\n")
                .reduce("", StringJoiner.join());
    }
    
    
    
    public byte[] getContent()
    {
        return content;
    }
    public String getContentString(String charset) throws UnsupportedEncodingException
    {
        return new String(content, charset);
    }
    public String getContentString() throws UnsupportedEncodingException
    {
        return getContentString("UTF-8");
    }
    public ByteArrayInputStream getContentStream()
    {
        return new ByteArrayInputStream(content);
    }
    public ExtendableByteBuffer getContentBuffer()
    {
        return new ExtendableByteBuffer().write(content);
    }
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc="Converters">
    public byte[] toBytes()
    {
        try
        {
            byte[] headers = (getHeadLine() + "\r\n" + getHeadersString() + "\r\n").getBytes("UTF-8");
            
            if(content.length > 0)
            {
                byte[] result = new byte[headers.length + content.length];

                System.arraycopy(headers, 0, result, 0, headers.length);
                System.arraycopy(content, 0, result, headers.length, content.length);
                return result;
            }
            else
                return headers;
        }
        catch (UnsupportedEncodingException ex)
        {
            return null;
        }
    }
    
    @Override
    public String toString()
    {
        String content = "";
        try
        {
            if(this.content.length > 0)
                content = getContentString();
        }
        catch (UnsupportedEncodingException ex)
        { }
        
        return getHeadLine() + "\r\n" + getHeadersString() + "\r\n" + content;
    }
    // </editor-fold>
    
    protected abstract String getHeadLine();
}
 public class HTTPResponse extends HTTPMessage
{
    public HTTPResponse(
            int code,
            String message,
            String httpVersion,
            Map<String, String> headers,
            byte[] content)
    {
        super(httpVersion, headers, content);
        this.code = code;
        this.message = message;
    }
    
    // <editor-fold defaultstate="collapsed" desc="Builder">
    public static Builder create()
    {
        return new Builder();
    }

    public static class Builder
    {
        private int code = 207;
        private String httpVersion = "HTTP/1.1";
        private String message = "Multi-Status";
        private Map<String, String> headers = new HashMap<>();
        private byte[] content = null;
        
        public Builder setHTTPVersion(double version)
        {
            this.httpVersion = "HTTP/" + version;
            return this;
        }
        public Builder setHTTPVersion(String version)
        {
            version = version.trim();
            
            if(!version.toUpperCase().startsWith("HTTP/"))
                version = "HTTP/" + version;
            
            this.httpVersion = version;
            return this;
        }
        
        public Builder setCode(String code)
        {
            this.code = Integer.parseInt(code);
            return this;
        }
        public Builder setCode(int code)
        {
            this.code = code;
            return this;
        }
        
        public Builder setContent(byte[] content)
        {
            this.content = content;
            return this;
        }
        public Builder setContent(String content)
        {
            return setContent(content, "UTF-8");
        }
        public Builder setContent(String content, String charset)
        {
            try
            {
                this.content = content.getBytes(charset);
            }
            catch(UnsupportedEncodingException ex)
            { }
            
            return this;
        }
        
        public Builder setHeaders(Map<String, String> headers)
        {
            this.headers.clear();
            addHeaders(headers);
            
            return this;
        }
        public Builder addHeaders(Map<String, String> headers)
        {
            headers.entrySet()
                    .stream()
                    .forEach(e -> this.headers.put(formatHeaderName(e.getKey()), e.getValue()));
            
            return this;
        }
        public Builder setHeader(String name, String value)
        {
            this.headers.put(formatHeaderName(name), value);
            return this;
        }
        
        public Builder setMessage(String message)
        {
            this.message = message.trim();
            return this;
        }
        
        
        public HTTPResponse build()
        {
            if(content == null)
                content = new byte[0];
            
            setHeader("Content-Length", String.valueOf(content.length));
            
            return new HTTPResponse(
                    code,
                    message,
                    httpVersion,
                    headers,
                    content);
        }
    }
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc="Parser">
    public static HTTPResponse parseHTTPResponse(byte[] byteMessage)
    {
        final Builder builder = create();
        
        parseHTTPMessage(byteMessage,
                headLine -> 
                {
                    builder.setHTTPVersion(headLine[0]);
                    builder.setCode(headLine[1]);
                    builder.setMessage(headLine[2]);
                },
                builder::setHeader,
                builder::setContent);
        
        return builder.build();
    }
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc="Properties">
    private final int code;
    private final String message;
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc="Accessors">
    public int getCode()
    {
        return code;
    }
    public String getMessage()
    {
        return message;
    }
    // </editor-fold>
    
    @Override
    protected String getHeadLine()
    {
        return httpVersion + " " + code + " " + message;
    }
    
    
    // <editor-fold defaultstate="collapsed" desc="Code manager">
    public <T extends Throwable> HTTPResponse throwCode(int code, Supplier<T> supplier) throws T
    {
        if(this.code == code)
            throw supplier.get();
        return this;
    }
    public <T extends Throwable> HTTPResponse throwCodes(int[] codes, Supplier<T> supplier) throws T
    {
        for(int code : codes)
            if(this.code == code)
                throw supplier.get();
        return this;
    }
    // </editor-fold>
}
 public class ExtendableByteBuffer
{
    public ExtendableByteBuffer()
    { }
    
    protected int internalBufferSize = 1500;
    protected List<byte[]> content = new ArrayList<>();
    protected int totalLength = 0;
    
    
    
    public ExtendableByteBuffer setInternalBufferSize(int internalBufferSize)
    {
        this.internalBufferSize = internalBufferSize;
        return this;
    }
    
    
    
    
    
    
    
    
    
    
    public ExtendableByteBuffer write(ExtendableByteBuffer byteBuffer)
    {
        content.addAll(byteBuffer.content);
        totalLength += byteBuffer.totalLength;
        
        return this;
    }
    public ExtendableByteBuffer write(InputStream is, int stopSize) throws IOException
    {
        byte[] data = new byte[internalBufferSize];
        int len;
        while((len = is.read(data)) >= 0)
            if(len > 0)
            {
                byte[] d = new byte[len];
                System.arraycopy(data, 0, d, 0, len);
                content.add(d);
                totalLength += len;
                
                if(totalLength >= stopSize)
                    return this;
            }
        
        return this;
    }
    public ExtendableByteBuffer write(InputStream is) throws IOException
    {
        byte[] data = new byte[internalBufferSize];
        int len;
        while((len = is.read(data)) >= 0)
            if(len > 0)
            {
                byte[] d = new byte[len];
                System.arraycopy(data, 0, d, 0, len);
                content.add(d);
                totalLength += len;
            }
        
        return this;
    }
    public ExtendableByteBuffer writeOnce(InputStream is) throws IOException
    {
        byte[] data = new byte[internalBufferSize];
        int len;
        if((len = is.read(data)) >= 0)
        {
            byte[] d = new byte[len];
            System.arraycopy(data, 0, d, 0, len);
            content.add(d);
            totalLength += len;
        }
        
        return this;
    }
    public ExtendableByteBuffer write(Byte value)
    {
        write((byte)value);
        return this;
    }
    public ExtendableByteBuffer write(byte value)
    {
        content.add(new byte[] { value });
        totalLength++;
        
        return this;
    }
    public ExtendableByteBuffer write(Character value)
    {
        write((char)value);
        return this;
    }
    public ExtendableByteBuffer write(char value)
    {
        content.add(new byte[] { (byte)value });
        totalLength++;
        
        return this;
    }
    public ExtendableByteBuffer write(byte[] values, int len)
    {
        if(len == 0)
            return this;
        
        byte[] newBytes = new byte[len];
        System.arraycopy(values, 0, newBytes, 0, len);
        content.add(newBytes);
        totalLength += newBytes.length;
        
        return this;
    }
    public ExtendableByteBuffer write(byte[] values, int len, int index)
    {
        if(len == 0 || index + len >= values.length)
            return this;
        
        byte[] newBytes = new byte[len];
        System.arraycopy(values, index, newBytes, 0, len);
        content.add(newBytes);
        totalLength += newBytes.length;
        
        return this;
    }
    public ExtendableByteBuffer write(byte[] values)
    {
        content.add(values);
        totalLength += values.length;
        
        return this;
    }
    public ExtendableByteBuffer write(char[] values, int len)
    {
        if(len == 0)
            return this;
        
        byte[] data = new byte[len];
        for(int i = 0; i < data.length; i++)
            data[i] = (byte)values[i];
        
        write(data);
        
        return this;
    }
    public ExtendableByteBuffer write(char[] values, int len, int index)
    {
        if(len == 0 || index + len >= values.length)
            return this;
        
        byte[] data = new byte[len];
        for(int i = 0; i < data.length; i++)
            data[i] = (byte)values[i + index];
        
        write(data);
        
        return this;
    }
    public ExtendableByteBuffer write(char[] values)
    {
        byte[] data = new byte[values.length];
        for(int i = 0; i < data.length; i++)
            data[i] = (byte)values[i];
        
        write(data);
        
        return this;
    }
    public ExtendableByteBuffer write(Byte[] values, int len)
    {
        if(len == 0)
            return this;
        
        byte[] data = new byte[len];
        for(int i = 0; i < data.length; i++)
            data[i] = (byte)values[i];
        
        write(data);
        
        return this;
    }
    public ExtendableByteBuffer write(Byte[] values, int len, int index)
    {
        if(len == 0 || index + len >= values.length)
            return this;
        
        byte[] data = new byte[len];
        for(int i = 0; i < data.length; i++)
            data[i] = (byte)values[i + index];
        
        write(data);
        
        return this;
    }
    public ExtendableByteBuffer write(Byte[] values)
    {
        byte[] data = new byte[values.length];
        for(int i = 0; i < data.length; i++)
            data[i] = (byte)values[i];
        
        write(data);
        
        return this;
    }
    public ExtendableByteBuffer write(Character[] values, int len)
    {
        if(len == 0)
            return this;
        
        byte[] data = new byte[len];
        for(int i = 0; i < data.length; i++)
            data[i] = (byte)(char)values[i];
        
        write(data);
        
        return this;
    }
    public ExtendableByteBuffer write(Character[] values, int len, int index)
    {
        if(len == 0 || index + len >= values.length)
            return this;
        
        byte[] data = new byte[len];
        for(int i = 0; i < data.length; i++)
            data[i] = (byte)(char)values[i + index];
        
        write(data);
        
        return this;
    }
    public ExtendableByteBuffer write(Character[] values)
    {
        byte[] data = new byte[values.length];
        for(int i = 0; i < data.length; i++)
            data[i] = (byte)(char)values[i];
        
        write(data);
        
        return this;
    }
    public ExtendableByteBuffer write(Iterable<Byte> values)
    {
        ArrayList<Byte> bytes = new ArrayList<>();
        values.forEach(c -> bytes.add(c));
        
        write(bytes.stream().toArray(Byte[]::new));
        
        return this;
    }
    
    
    
    
    
    
    
    
    public int length()
    {
        return totalLength;
    }
    
    
    
    
    
    
    
    
    
    
    
    public ByteBuffer toByteBuffer()
    {
        byte[] contentData = new byte[totalLength];
        int index = 0;
        for(byte[] bs : content)
        {
            System.arraycopy(bs, 0, contentData, index, bs.length);
            index += bs.length;
        }
        
        return ByteBuffer.wrap(contentData);
    }
    public byte[] toBytes()
    {
        return toByteBuffer().array();
    }
    public char[] toChars()
    {
        return toByteBuffer().asCharBuffer().array();
    }
    public double[] toDoubles()
    {
        return toByteBuffer().asDoubleBuffer().array();
    }
    public float[] toFloats()
    {
        return toByteBuffer().asFloatBuffer().array();
    }
    public int[] toInts()
    {
        return toByteBuffer().asIntBuffer().array();
    }
    public long[] toLongs()
    {
        return toByteBuffer().asLongBuffer().array();
    }
    public short[] toShorts()
    {
        return toByteBuffer().asShortBuffer().array();
    }
    @Override
    public String toString()
    {
        return new String(toBytes());
    }
    public String toString(String charset) throws UnsupportedEncodingException
    {
        return new String(toBytes(), charset);
    }
    public String toString(Charset charset)
    {
        return new String(toBytes(), charset);
    }
    public Collection<String> toLines()
    {
        return Arrays.asList(toString()
                .replace("\r\n", "\n")
                .replace("\r", "\n")
                .split("\n"));
    }
    public Collection<String> toLines(String charset) throws UnsupportedEncodingException
    {
        return Arrays.asList(toString(charset)
                .replace("\r\n", "\n")
                .replace("\r", "\n")
                .split("\n"));
    }
    public Collection<String> toLines(Charset charset)
    {
        return Arrays.asList(toString(charset)
                .replace("\r\n", "\n")
                .replace("\r", "\n")
                .split("\n"));
    }
    
    public Document toXML() throws ParserConfigurationException, SAXException, IOException
    {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);

        DocumentBuilder xmlBuilder = factory.newDocumentBuilder();

        ByteArrayInputStream bais = new ByteArrayInputStream(toBytes());
        return xmlBuilder.parse(bais);
    }
}
 public class FileSystemPath
{
    public FileSystemPath(String fileName, FileSystemPath parent)
    {
        this.fileName = fileName;
        this.parent = parent;
        this.fileSystemPathManager = parent.fileSystemPathManager;
    }
    public FileSystemPath(String fileName, FileSystemPathManager fileSystemPathManager)
    {
        this.fileName = fileName;
        this.parent = null;
        this.fileSystemPathManager = fileSystemPathManager;
    }
    
    private final String fileName;
    private final FileSystemPath parent;
    private final FileSystemPathManager fileSystemPathManager;
    
    public String getName()
    {
        return fileName;
    }
    
    public FileSystemPath getParent()
    {
        return parent;
    }
    
    public boolean isRoot()
    {
        return parent == null;
    }
    
    public FileSystemPath createChild(String childName)
    {
        return new FileSystemPath(childName, this);
    }
    
    
    public LinkedList<FileSystemPath> toPaths()
    {
        LinkedList<FileSystemPath> paths = toReversePaths();
        Collections.reverse(paths);
        return paths;
    }
    public LinkedList<FileSystemPath> toReversePaths()
    {
        LinkedList<FileSystemPath> paths = new LinkedList<>();
        
        FileSystemPath path = this;
        do
        {
            paths.add(path);
        } while((path = path.getParent()) != null && !path.isRoot());
        
        return paths;
    }
    
    public String[] toStrings()
    {
        return toString().split(fileSystemPathManager.standardFileSeparator);
    }

    @Override
    public String toString()
    {
        if(isRoot())
            return getName();
        
        String result = getParent() + fileSystemPathManager.standardFileSeparator + getName();
        if(result.startsWith(fileSystemPathManager.standardFileSeparator + fileSystemPathManager.standardFileSeparator))
            result = result.substring(1);
        return result;
    }

    @Override
    public boolean equals(Object o)
    {
        return o instanceof FileSystemPath && o.hashCode() == this.hashCode();
    }
    @Override
    public int hashCode()
    {
        return toString().hashCode();
    }
}
 public class UnexpectedException extends HTTPException
{
    public UnexpectedException(Throwable ex)
    {
        super("Not expected error", ex);
    }
}
 public interface IResource
{
    /**
     * Get if the resource is visible.
     * 
     * @param env
     * @return boolean
     * @throws http.server.exceptions.UserRequiredException
     */
    public boolean isVisible(HTTPEnvRequest env) throws UserRequiredException;
    
    /**
     * Get the type of the resource.
     * 
     * @param env
     * @return ResourceType
     * @throws http.server.exceptions.UserRequiredException
     */
    public ResourceType getResourceType(HTTPEnvRequest env) throws UserRequiredException;
    
    /**
     * Get the web name of the resource.
     * 
     * @param env
     * @return String
     * @throws http.server.exceptions.UserRequiredException
     */
    public String getWebName(HTTPEnvRequest env) throws UserRequiredException;
    /**
     * Get the resource name.
     * 
     * @param env
     * @return String
     * @throws http.server.exceptions.UserRequiredException
     */
    //public FileSystemPath getPath(HTTPEnvRequest env) throws UserRequiredException;
    
    /**
     * Get the mime type of the resource.
     * 
     * @param env
     * @return String
     * @throws http.server.exceptions.UserRequiredException
     */
    public String getMimeType(HTTPEnvRequest env) throws UserRequiredException;
    /**
     * Get the size of the resource (byte).
     * 
     * @param env
     * @return long
     * @throws http.server.exceptions.UserRequiredException
     */
    public long getSize(HTTPEnvRequest env) throws UserRequiredException;
    /**
     * Get the creation time.
     * 
     * @param env
     * @return Instant
     * @throws http.server.exceptions.UserRequiredException
     */
    public Instant getCreationTime(HTTPEnvRequest env) throws UserRequiredException;
    /**
     * Get the last modified date.
     * 
     * @param env
     * @return Instant
     * @throws http.server.exceptions.UserRequiredException
     */
    public Instant getLastModified(HTTPEnvRequest env) throws UserRequiredException;
    /**
     * Get the list of the resources contained in this resource.
     * 
     * @param env
     * @return Collection of IResource
     * @throws http.server.exceptions.UserRequiredException
     */
    public Collection<IResource> listResources(HTTPEnvRequest env) throws UserRequiredException;
    
    /**
     * Get the content of the resource.
     * 
     * @param env
     * @return byte[]
     * @throws http.server.exceptions.UserRequiredException
     */
    public byte[] getContent(HTTPEnvRequest env) throws UserRequiredException;
    /**
     * Set the content of the resource.
     * 
     * @param content Content to put in the resource
     * @param env
     * @throws http.server.exceptions.UserRequiredException
     */
    public void setContent(byte[] content, HTTPEnvRequest env) throws UserRequiredException;
    /**
     * Append a content to the resource.
     * 
     * @param content Content to append in the resource
     * @param env
     * @throws http.server.exceptions.UserRequiredException
     */
    public void appendContent(byte[] content, HTTPEnvRequest env) throws UserRequiredException;
    
    /**
     * Delete the resource.
     * 
     * @param env
     * @return boolean
     * @throws http.server.exceptions.UserRequiredException
     */
    public boolean delete(HTTPEnvRequest env) throws UserRequiredException;
    /**
     * Rename or move a resource from the current path to 'resource' path.
     * 
     * @param newPath
     * @param env
     * @return boolean
     * @throws http.server.exceptions.UserRequiredException
     */
    public boolean moveTo(FileSystemPath oldPath, FileSystemPath newPath, HTTPEnvRequest env) throws UserRequiredException;
    
    public boolean rename(String newName, HTTPEnvRequest env) throws UserRequiredException;
    
    /**
     * Define if the resource exists or not.
     * 
     * @param env
     * @return boolean
     * @throws UserRequiredException 
     */
    public boolean exists(HTTPEnvRequest env) throws UserRequiredException;
    
    public IResource creates(ResourceType resourceType, HTTPEnvRequest env) throws UserRequiredException;
    public IResource creates(IResource resource, HTTPEnvRequest env) throws UserRequiredException;
    
    public boolean isOnTheSameFileSystemWith(IResource resource);
    
    public IResource getResource(LinkedList<FileSystemPath> reversedPath, HTTPEnvRequest env) throws UserRequiredException;
    
    
    public boolean equals(FileSystemPath path, HTTPEnvRequest env);
    
    
    public boolean addChild(IResource resource, HTTPEnvRequest env);
    public boolean removeChild(IResource resource, HTTPEnvRequest env);
    
    public boolean isInstanceOf(Class<?> c);
    
    
    
    // <editor-fold defaultstate="collapsed" desc="Properties">
    public void setProperty(String namespace, String name, String value, HTTPEnvRequest env) throws UserRequiredException;
    public void removeProperty(String name, HTTPEnvRequest env) throws UserRequiredException;
    public void removeProperty(String namespace, String name, HTTPEnvRequest env) throws UserRequiredException;
    public String getProperty(String name, HTTPEnvRequest env) throws UserRequiredException;
    public String getProperty(String namespace, String name, HTTPEnvRequest env) throws UserRequiredException;
    public Map<Pair<String, String>, String> getProperties(HTTPEnvRequest env) throws UserRequiredException;
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc="Locks">
    public Collection<LockKind> getAvailableLocks() throws UserRequiredException;
    public List<Lock> getLocks(HTTPEnvRequest env) throws UserRequiredException;
    public List<Lock> getLocks(LockKind lockKind, HTTPEnvRequest env) throws UserRequiredException;
    public boolean setLock(Lock lock, HTTPEnvRequest env) throws UserRequiredException;
    public boolean setLock(LockKind lockKind, HTTPEnvRequest env) throws UserRequiredException;
    public void removeLock(Lock lock, HTTPEnvRequest env) throws UserRequiredException;
    public void removeLock(String uuid, HTTPEnvRequest env) throws UserRequiredException;
    public boolean canLock(LockKind lockKind, HTTPEnvRequest env) throws UserRequiredException;
    // </editor-fold>
}
 public class NotFoundException extends HTTPException
{
    public NotFoundException()
    {
        super("Resource not found");
    }
    
    public static HTTPResponse.Builder getHTTPResponse()
    {
        return HTTPResponse.create()
                .setCode(404)
                .setMessage("Not found");
    }
}
 public class UserRequiredException extends HTTPException
{
    public UserRequiredException()
    {
        super("User required");
    }
}
 public class HTTPEnvRequest
{
    public static Builder create()
    {
        return new Builder();
    }
    public static class Builder
    {
        public Builder()
        { }
        
        private HTTPRequest request;
        private HTTPCommand command;
        private HTTPServerSettings settings;
        private Collection<IResourceMutation> mutations = null;
        private byte[] bytesReceived = null;
        
        public Builder setRequest(HTTPRequest request)
        {
            this.request = request;
            return this;
        }
        
        public Builder setCommand(HTTPCommand command)
        {
            this.command = command;
            return this;
        }
        
        public Builder setBytesReceived(byte[] bytesReceived)
        {
            this.bytesReceived = bytesReceived;
            return this;
        }
        
        public Builder setSettings(HTTPServerSettings settings)
        {
            this.settings = settings;
            return this;
        }
        
        public Builder setMutations(Collection<IResourceMutation> mutations)
        {
            this.mutations = mutations;
            return this;
        }
        public Builder addMutations(IResourceMutation[] mutations)
        {
            this.mutations.addAll(Arrays.asList(mutations));
            return this;
        }
        public Builder addMutations(Collection<IResourceMutation> mutations)
        {
            this.mutations.addAll(mutations);
            return this;
        }
        public Builder addMutation(IResourceMutation mutation)
        {
            this.mutations.add(mutation);
            return this;
        }
        
        public HTTPEnvRequest build()
        {
            if(mutations == null)
                mutations = Collections.EMPTY_LIST;
            
            return new HTTPEnvRequest(
                    bytesReceived,
                    request,
                    command,
                    settings.getAuthenticationManager().checkAuth(request),
                    settings,
                    settings.getFileManager().getResourceMutations());
        }
    }
    
    public HTTPEnvRequest(
            byte[] bytesReceived,
            HTTPRequest request,
            HTTPCommand command,
            HTTPUser user,
            HTTPServerSettings settings,
            Collection<IResourceMutation> mutations)
    {
        this.bytesReceived = bytesReceived;
        this.request = request;
        this.command = command;
        this.user = user;
        this.settings = settings;
        this.mutations = mutations;
    }
    
    private byte[] bytesReceived;
    private FileSystemPath path = null;
    
    private final HTTPRequest request;
    private final HTTPCommand command;
    private final HTTPUser user;
    private final HTTPServerSettings settings;
    private final Collection<IResourceMutation> mutations;
    
    public FileSystemPath getPath()
    {
        if(path == null)
            path = settings.getFileSystemPathManager()
                    .createFromString(request.getDecodedPath());
        
        return path;
    }
    
    public Collection<IResourceMutation> getMutations()
    {
        return mutations;
    }
    
    public byte[] getBytesReceived()
    {
        if(bytesReceived == null)
            bytesReceived = request.toBytes();
        return bytesReceived;
    }
    
    public HTTPRequest getRequest()
    {
        return request;
    }
    
    public HTTPCommand getCommand()
    {
        return command;
    }
    
    public HTTPUser getUser()
    {
        return user;
    }
    
    public HTTPServerSettings getSettings()
    {
        return settings;
    }
}
 public class WD_Delete extends HTTPCommand
{
    public WD_Delete()
    {
        super("delete");
    }
    
    @Override
    public HTTPResponse.Builder Compute(HTTPEnvRequest environment) throws UserRequiredException, NotFoundException 
    {
        FileSystemPath path = environment.getPath();
        IResource resource = getResource(path, environment);
        IResource resourceParent = getResource(path.getParent(), environment);
        resourceParent.removeChild(resource, environment);
        resource.delete(environment);
        
        return HTTPResponse.create()
                .setCode(200)
                .setMessage("OK")
                .setHeader("Content-Type", "text/xml; charset=\"utf-8\"");
    }
    
}
 public class WD_Get extends HTTPCommand
{
    public WD_Get()
    {
        super("get");
    }
    WD_Get(String name)
    {
        super(name);
    }
    
    @Override
    public HTTPResponse.Builder Compute(HTTPEnvRequest environment) throws UserRequiredException, NotFoundException 
    {
        try
        {
        IResource f = getResource(environment.getPath(), environment);
           
        HTTPResponse.Builder builder = HTTPResponse.create()
                .setCode(200)
                .setMessage("OK");
        
        switch(f.getResourceType(environment))
        {
            case File:
                builder.setContent(f.getContent(environment))
                        .setHeader("Content-Type", f.getMimeType(environment));
                break;
                
            case Directory:
                String content = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><resources>";

                for(IResource sf : f.listResources(environment))
                    switch(sf.getResourceType(environment))
                    {
                        case File:
                            content += "<directory>" + sf.getWebName(environment) + "</directory>";
                            break;

                        case Directory:
                            content += "<file><name>" + sf.getWebName(environment) + "</name><size>" + sf.getSize(environment) + "</size><type>" + sf.getMimeType(environment) + "</type></file>";
                            break;
                    }
                content += "</resources>";

                builder.setContent(content);
                break;
        }
        
        return builder;
        }
        catch(Exception ex)
        {
            ex.printStackTrace();
            throw ex;
        }
    }
    
}
 public class WD_Head extends HTTPCommand
{
    public WD_Head()
    {
        super("head");
    }
    
    @Override
    public HTTPResponse.Builder Compute(HTTPEnvRequest environment) throws UserRequiredException, NotFoundException 
    {
        IResource f = getResource(environment.getPath(), environment);
        
        return HTTPResponse.create()
                .setCode(200)
                .setMessage("OK")
                .setHeader("Content-Length", String.valueOf(f.getSize(environment)))
                .setHeader("Content-Type", "text/xml; charset=\"utf-8\"");
    }
    
}
 public class WD_Lock extends HTTPCommand
{
    public WD_Lock()
    {
        super("lock");
    }
    
    
    private Lock getRequestedLock(HTTPMessage input)
    {
        try
        {
            Document doc = createDocument(input);
            
            
            LockScope lockscope = NodeListWrap
                    .getStream(doc.getElementsByTagNameNS("*", "lockscope"))
                    .filter(Node::hasChildNodes)
                    .map(Node::getFirstChild)
                    .map(Node::getLocalName)
                    .map(t -> t.substring(0, 1).toUpperCase() + t.substring(1).toLowerCase())
                    .map(LockScope::valueOf)
                    .findFirst()
                    .orElse(null);
            
            LockType locktype = NodeListWrap
                    .getStream(doc.getElementsByTagNameNS("*", "locktype"))
                    .filter(Node::hasChildNodes)
                    .map(Node::getFirstChild)
                    .map(Node::getLocalName)
                    .map(String::toLowerCase)
                    .map(LockType::valueOf)
                    .findFirst()
                    .orElse(null);
            
            String owner = NodeListWrap
                    .getStream(doc.getElementsByTagNameNS("*", "owner"))
                    .filter(Node::hasChildNodes)
                    .map(Node::getFirstChild)
                    .filter(n -> "href".equals(n.getLocalName().toLowerCase()))
                    .map(Node::getTextContent)
                    .filter(t -> !t.trim().isEmpty())
                    .findFirst()
                    .orElse(null);
            
            if(lockscope == null || locktype == null)
                return null;
            
            LockKind lk = new LockKind(lockscope, locktype);
            
            if(owner == null || owner.trim().isEmpty())
                return new Lock(lk);
            else
                return new Lock(lk, owner);
        }
        catch (ParserConfigurationException | SAXException | IOException ex)
        {
            return null;
        }
    }
    
    @Override
    public HTTPResponse.Builder Compute(HTTPEnvRequest environment) throws NotFoundException, UserRequiredException
    {
        FileSystemPath path = environment.getPath();
        IResource resource = getResource(path, environment);
        
        if(!resource.exists(environment))
        {
            getResource(path.getParent(), environment)
                    .addChild(resource.creates(ResourceType.File, environment), environment);
        }
        
        Lock lock = getRequestedLock(environment.getRequest());
        
        if(lock == null)
            throw new NotFoundException();
        
        LockKind lockKind = lock.getLockKind();
        
        if(!resource.canLock(lockKind, environment))
        {
            return HTTPResponse.create()
                    .setCode(432)
                    .setMessage("Locked")
                    .setHeader("Content-Type", "text/xml; charset=\"utf-8\"");
        }
        
        resource.setLock(lock, environment);
        
        try
        {
            Document doc = createDocument();
            
            Element value;
            
        
            Element prop = doc.createElementNS("DAV:", "prop");
            doc.appendChild(prop);
            
            Element lockdiscovery = doc.createElementNS("DAV:", "lockdiscovery");
            prop.appendChild(lockdiscovery);
            
            Element activelock = doc.createElementNS("DAV:", "activelock");
            lockdiscovery.appendChild(activelock);
            
            Element depth = doc.createElementNS("DAV:", "depth");
            depth.setTextContent("infinity");
            activelock.appendChild(depth);
            
            Element timeout = doc.createElementNS("DAV:", "timeout");
            timeout.setTextContent("Second-" + lockKind.getTimeout());
            activelock.appendChild(timeout);
            
            if(lock.getOwner() != null)
            {
                Element owner = doc.createElementNS("DAV:", "owner");
                value = doc.createElementNS("DAV:", "href");
                value.setTextContent(lock.getOwner());
                owner.appendChild(value);
                activelock.appendChild(owner);
            }
            
            Element locktoken = doc.createElementNS("DAV:", "locktoken");
            value = doc.createElementNS("DAV:", "href");
            value.setTextContent(lock.getUUID());
            locktoken.appendChild(value);
            activelock.appendChild(locktoken);
            
            Element lockroot = doc.createElementNS("DAV:", "lockroot");
            value = doc.createElementNS("DAV:", "href");
            value.setTextContent(environment.getRequest().getPath());
            lockroot.appendChild(value);
            activelock.appendChild(lockroot);
            
            Element locktype = doc.createElementNS("DAV:", "locktype");
            value = doc.createElementNS("DAV:", lockKind.getType().name().toLowerCase());
            locktype.appendChild(value);
            activelock.appendChild(locktype);
            
            Element lockscope = doc.createElementNS("DAV:", "lockscope");
            value = doc.createElementNS("DAV:", lockKind.getScope().name().toLowerCase());
            lockscope.appendChild(value);
            activelock.appendChild(lockscope);
            
            
            
            
            return HTTPResponse.create()
                    .setCode(200)
                    .setMessage("OK")
                    .setHeader("Lock-Token", "<" + lock.getUUID() + ">")
                    .setHeader("Content-Type", "text/xml; charset=\"utf-8\"")
                    .setContent(NodeListWrap.getBytes(doc));
        }
        catch (ParserConfigurationException ex)
        {
            throw new NotFoundException();
        }
    }
    
}
 public class WD_Mkcol extends HTTPCommand
{
    public WD_Mkcol()
    {
        super("mkcol");
    }
    
    @Override
    public HTTPResponse.Builder Compute(HTTPEnvRequest environment) throws UserRequiredException 
    {
        FileSystemPath path = environment.getPath();
        getResource(path.getParent(), environment)
                .addChild(getResource(environment.getPath(), environment)
                        .creates(ResourceType.Directory, environment)
                        , environment);
        
        return HTTPResponse.create()
                .setCode(201)
                .setMessage("Created");
    }
    
}
 public class WD_Move extends HTTPCommand
{
    public WD_Move()
    {
        super("move");
    }
    
    @Override
    public HTTPResponse.Builder Compute(HTTPEnvRequest environment) throws UserRequiredException, NotFoundException, UnexpectedException 
    {
        String dest = environment.getRequest().getHeader("destination");
        
        try
        {
            FileSystemPath source = environment.getPath();
            String host = environment.getRequest().getHeader("host");
            String localDest = URLDecoder.decode(dest.substring(dest.indexOf(host) + host.length()), "UTF-8");

            getResource(source, environment)
                    .moveTo(source
                            , environment.getSettings()
                                    .getFileSystemPathManager()
                                    .createFromString(localDest)
                            , environment);
        }
        catch (UnsupportedEncodingException ex)
        {
            throw new UnexpectedException(ex);
        }
        
        return HTTPResponse.create()
                .setCode(201)
                .setMessage("Created")
                .setHeader("Location", dest)
                .setHeader("Content-Type", "text/xml; charset=\"utf-8\"");
    }
    
}
 public class WD_Options extends HTTPCommand
{
    public WD_Options()
    {
        super("options");
    }

    @Override
    public HTTPResponse.Builder Compute(HTTPEnvRequest environment) 
    {
        String cmds = environment.getSettings()
                .getAllowedCommands()
                .stream()
                .map(HTTPCommand::getName)
                .reduce("", StringJoiner.join(", "));
        
        return HTTPResponse.create()
                .setCode(200)
                .setMessage("OK")
                .setHeader("Allow", cmds)
                .setHeader("Content-Type", "httpd/unix-directory");
    }
    
}
 public class WD_Post extends WD_Get
{
    public WD_Post()
    {
        super("post");
    }
    
}
 public class WD_Propfind extends HTTPCommand
{
    public WD_Propfind()
    {
        super("propfind");
    }
    
    private Element getInfo(IResource resource, FileSystemPath path, String host, HTTPEnvRequest environment, Document doc) throws UserRequiredException, UnsupportedEncodingException
    {
        if(!resource.isVisible(environment))
            return null;
        
        String displayName = resource.getWebName(environment);
        Instant creationTime = resource.getCreationTime(environment);

        if(displayName == null || creationTime == null)
            return null;

        Element response = doc.createElementNS("DAV:", "response");

        Element href = doc.createElementNS("DAV:", "href");
        href.setTextContent(getHostPath(path.toString(), host));
        response.appendChild(href);

        Element propstat = doc.createElementNS("DAV:", "propstat");
        response.appendChild(propstat);

        Element status = doc.createElementNS("DAV:", "status");
        status.setTextContent("HTTP/1.1 200 OK");
        propstat.appendChild(status);

        Element prop = doc.createElementNS("DAV:", "prop");
        propstat.appendChild(prop);

        Element creationdate = doc.createElementNS("DAV:", "creationdate");
        creationdate.setTextContent(FileTime.from(creationTime).toString().substring(0, "0000-00-00T00:00:00".length()) + "-00:00");
        prop.appendChild(creationdate);

        Element displayname = doc.createElementNS("DAV:", "displayname");
        displayname.setTextContent(URLDecoder.decode(displayName, "UTF-8"));
        prop.appendChild(displayname);

        Element supportedlock = doc.createElementNS("DAV:", "supportedlock");
        prop.appendChild(supportedlock);

        resource.getAvailableLocks()
                .stream()
                .forEach(lk ->
                {
                    Element value;

                    Element lockentry = doc.createElementNS("DAV:", "lockentry");
                    supportedlock.appendChild(lockentry);

                    Element lockscope = doc.createElementNS("DAV:", "lockscope");
                    value = doc.createElementNS("DAV:", lk.getScope().name().toLowerCase());
                    lockscope.appendChild(value);
                    lockentry.appendChild(lockscope);

                    Element locktype = doc.createElementNS("DAV:", "locktype");
                    value = doc.createElementNS("DAV:", lk.getType().name().toLowerCase());
                    locktype.appendChild(value);
                    lockentry.appendChild(locktype);
                });
        
        resource.getProperties(environment)
                .entrySet()
                .forEach(p ->
                {
                    Element property = doc.createElementNS(p.getKey().getKey(), p.getKey().getValue());
                    property.setTextContent(p.getValue());
                    prop.appendChild(property);
                });

        switch(resource.getResourceType(environment))
        {
            case File:
                if(!getInfoFile(resource, host, environment, doc, prop))
                    return null;
                break;

            case Directory:
                if(!getInfoFolder(resource, host, environment, doc, prop))
                    return null;
                break;
        }
        
        return response;
    }
    private boolean getInfoFolder(IResource resource, String host, HTTPEnvRequest environment, Document doc, Element parent) throws UserRequiredException
    {
        Element resourcetype = doc.createElementNS("DAV:", "resourcetype");
        parent.appendChild(resourcetype);
        
        Element collection = doc.createElementNS("DAV:", "collection");
        resourcetype.appendChild(collection);
        
        return true;
    }
    private boolean getInfoFile(IResource resource, String host, HTTPEnvRequest environment, Document doc, Element parent) throws UserRequiredException
    {
        Instant lastModified = resource.getLastModified(environment);
        String mimeType = resource.getMimeType(environment);
        if(mimeType == null)
            mimeType = "text/binary";
        
        if(lastModified == null)
            return false;
        
        
        Element resourcetype = doc.createElementNS("DAV:", "resourcetype");
        parent.appendChild(resourcetype);
        
        Element getcontentlength = doc.createElementNS("DAV:", "getcontentlength");
        getcontentlength.setTextContent(String.valueOf(resource.getSize(environment)));
        parent.appendChild(getcontentlength);
        
        Element getcontenttype = doc.createElementNS("DAV:", "getcontenttype");
        getcontenttype.setTextContent(mimeType);
        parent.appendChild(getcontenttype);
        
        Element getetag = doc.createElementNS("DAV:", "getetag");
        getetag.setTextContent("zzyzx");
        parent.appendChild(getetag);
        
        Element getlastmodified = doc.createElementNS("DAV:", "getlastmodified");
        getlastmodified.setTextContent(Helper.toString(Date.from(lastModified)));
        parent.appendChild(getlastmodified);
        
        return true;
    }

    @Override
    public HTTPResponse.Builder Compute(HTTPEnvRequest environment) throws UserRequiredException, NotFoundException 
    {
        FileSystemPath path = environment.getPath();
        IResource f = getResource(path, environment);
        
        String host = environment.getRequest().getHeader("host");
        
        try
        {
            Document doc = createDocument();
        
            Element multistatus = doc.createElementNS("DAV:", "multistatus");
            doc.appendChild(multistatus);
            
            Element current = getInfo(f, path, host, environment, doc);
            if(current == null)
                throw new NotFoundException();
            multistatus.appendChild(current);
            
            if(environment.getRequest().getHeader("depth").trim().equals("0"))
            { // depth = 0
            }
            else
            { // depth = 1
                for(IResource subFile : f.listResources(environment))
                {
                    try
                    {
                        Element child = getInfo(subFile, path.createChild(subFile.getWebName(environment)), host, environment, doc);
                        if(child != null)
                            multistatus.appendChild(child);
                    }
                    catch(NotFoundException ex)
                    { }
                }
            }
        
            return HTTPResponse.create()
                    .setCode(207)
                    .setMessage("Multi-Status")
                    .setHeader("Content-Type", "text/xml; charset=\"utf-8\"")
                    .setContent(NodeListWrap.getBytes(doc));
        }
        catch (ParserConfigurationException | UnsupportedEncodingException ex)
        {
            throw new UnexpectedException(ex);
        }
    }

    
}
 public class WD_Proppatch extends HTTPCommand
{
    public WD_Proppatch()
    {
        super("proppatch");
    }
    
    private Collection<Node> getNodes(Document doc, String operationName)
    {
        return NodeListWrap
                .getStream(doc.getElementsByTagNameNS("*", operationName.toLowerCase()))
                .filter(Node::hasChildNodes)
                .map(Node::getFirstChild)
                .map(Node::getChildNodes)
                .flatMap(ns -> NodeListWrap.getStream(ns))
                .collect(Collectors.toList());
    }
    
    
    
    private Document generateResult(Stream<Node> totalStream, IResource resource, FileSystemPath path, String host, HTTPEnvRequest environment) throws UserRequiredException, ParserConfigurationException
    {
        final Document doc = createDocument();

        Element root = doc.createElementNS("DAV:", "multistatus");
        doc.appendChild(root);

        Element response = doc.createElementNS("DAV:", "response");
        root.appendChild(response);

        Element aResponse = doc.createElementNS("DAV:", "href");
        aResponse.setTextContent(getHostPath(path.toString(), host));
        response.appendChild(aResponse);
        
        totalStream
                .map(node ->
                {
                    Element propstat = doc.createElementNS("DAV:", "propstat");

                    Element status = doc.createElementNS("DAV:", "status");
                    status.setTextContent("HTTP/1.1 200 OK");
                    propstat.appendChild(status);

                    Element prop = doc.createElementNS("DAV:", "prop");
                    propstat.appendChild(prop);

                    Element nodeElement = doc.createElementNS(node.getNamespaceURI(), node.getLocalName());
                    prop.appendChild(nodeElement);
                    
                    return propstat;
                })
                .forEach(response::appendChild);
        
        return doc;
    }
    
    @Override
    public HTTPResponse.Builder Compute(HTTPEnvRequest environment) throws UserRequiredException, NotFoundException 
    {
        FileSystemPath path = environment.getPath();
        IResource resource = getResource(path, environment);
        String host = environment.getRequest().getHeader("host");
        
        byte[] content = new byte[0];
        
        try
        {
            Document doc = createDocument(environment.getRequest());
            
            doc.getDocumentElement().normalize();
            
            Collection<Node> setStream = getNodes(doc, "set");
            Collection<Node> removeStream = getNodes(doc, "remove");
            
            Stream<Node> totalStream = Stream.concat(setStream.stream(), removeStream.stream());
            
            for(Node n : setStream)
                resource.setProperty(n.getNamespaceURI(), n.getLocalName(), n.getTextContent(), environment);
            for(Node n : removeStream)
                resource.removeProperty(n.getNamespaceURI(), n.getLocalName(), environment);
            
            content = NodeListWrap.getBytes(generateResult(totalStream, resource, path, host, environment));
        }
        catch (ParserConfigurationException | SAXException | IOException | DOMException ex)
        {
            throw new UnexpectedException(ex);
        }
        
        return HTTPResponse.create()
                .setCode(207)
                .setMessage("Multi-Status ")
                .setContent(content)
                .setHeader("Content-Type", "text/xml; charset=\"utf-8\"");
    }
    
}
 public class WD_Put extends HTTPCommand
{
    public WD_Put()
    {
        super("put");
    }
    
    @Override
    public HTTPResponse.Builder Compute(HTTPEnvRequest environment) throws UserRequiredException, NotFoundException
    {
        FileSystemPath path = environment.getPath();
        IResource resource = getResource(path, environment);
        
        if(environment.getRequest().getHeader("content-length").equals("0"))
        { // Content-Length == 0
            getResource(path.getParent(), environment)
                    .addChild(resource.creates(ResourceType.File, environment), environment);
        }
        else
        { // Content-Length != 0
            resource.setContent(environment.getRequest().getContent(), environment);
        }
        
        return HTTPResponse.create()
                .setCode(200)
                .setMessage("OK")
                .setHeader("Content-Type", "text/xml; charset=\"utf-8\"");
    }
    
    @Override
    public void Continue(HTTPEnvRequest environment) throws UserRequiredException, NotFoundException
    {
        System.err.println("XXXXXXXXXXXXXXXXXXXXXXXXXXX");
        if(environment.getBytesReceived().length > 0)
            getResource(environment.getPath(), environment).appendContent(environment.getBytesReceived(), environment);
    }
}
 public class WD_Unlock extends HTTPCommand
{
    public WD_Unlock()
    {
        super("unlock");
    }
    
    @Override
    public HTTPResponse.Builder Compute(HTTPEnvRequest environment) throws NotFoundException, UserRequiredException 
    {
        String uuid = environment.getRequest().getHeader("Lock-Token").replace("<", "").replace(">", "");
        
        IResource resource = getResource(environment.getPath(), environment);
        
        resource.removeLock(uuid, environment);
        
        return HTTPResponse.create()
                .setCode(204)
                .setMessage("No Content")
                .setHeader("Lock-Token", "<" + uuid + ">")
                .setHeader("Content-Type", "text/xml; charset=\"utf-8\"");
    }
    
}

import http.server.message.HTTPMessage;
import http.server.message.HTTPResponse;
import http.ExtendableByteBuffer;
import http.FileSystemPath;
import http.server.exceptions.UnexpectedException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import webdav.server.resource.IResource;
import http.server.exceptions.NotFoundException;
import http.server.exceptions.UserRequiredException;
import http.server.message.HTTPEnvRequest;
import webdav.server.commands.WD_Delete;
import webdav.server.commands.WD_Get;
import webdav.server.commands.WD_Head;
import webdav.server.commands.WD_Lock;
import webdav.server.commands.WD_Mkcol;
import webdav.server.commands.WD_Move;
import webdav.server.commands.WD_Options;
import webdav.server.commands.WD_Post;
import webdav.server.commands.WD_Propfind;
import webdav.server.commands.WD_Proppatch;
import webdav.server.commands.WD_Put;
import webdav.server.commands.WD_Unlock;

package http.server;


public abstract class HTTPCommand
{
    public HTTPCommand(String command)
    {
        this.name = command.trim().toUpperCase();
        openedResources = new HashMap<>();
    }
    
    private final String name;
    
    /**
     * Get the name of the command.
     * 
     * @return String
     */
    public String getName()
    {
        return name;
    }
    
    /**
     * Execute the command with the HTTPMessage 'input' and in the 'environment'.
     * 
     * @param input Received HTTP message
     * @param environment Server environment
     * @return HTTPMessage
     * @throws http.server.exceptions.UserRequiredException
     */
    public abstract HTTPResponse.Builder Compute(HTTPEnvRequest environment) throws UserRequiredException, NotFoundException, UnexpectedException;
    
    public void Continue(HTTPEnvRequest environment) throws UserRequiredException, NotFoundException
    { }
    
    /**
     * Find a HTTPCommand in 'commands' with the command name 'command'.
     * If no HTTPCommand found, returns null.
     * 
     * @param commands Set of HTTPCommand to search in.
     * @param command Command name to find.
     * @return HTTPCommand
     */
    public static HTTPCommand getFrom(Set<HTTPCommand> commands, String command)
    {
        return commands.stream()
                .filter(c -> c.name.equals(command.trim().toUpperCase()))
                .findFirst()
                .orElse(null);
    }
    
    /**
     * Generate the command list with : OPTIONS, PROPFIND, PROPPATCH, MKCOL,
     * HEAD, GET, PUT, DELETE, LOCK, UNLOCK and MOVE.
     * 
     * @return HTTPCommand[]
     */
    public static HTTPCommand[] getStandardCommands()
    {
        return new HTTPCommand[]
        {
            new WD_Options(),
            new WD_Propfind(),
            new WD_Proppatch(),

            new WD_Mkcol(),
