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 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(),
            new WD_Head(),
            new WD_Post(),
            new WD_Get(),
            new WD_Put(),
            new WD_Delete(),
            new WD_Lock(),
            new WD_Unlock(),
            new WD_Move()
        };
    }

    @Override
    public boolean equals(Object obj)
    {
        return obj instanceof HTTPCommand && this.getName().equals(((HTTPCommand)obj).getName())
                || obj instanceof String && this.getName().equals(obj.toString());
    }

    @Override
    public int hashCode()
    {
        return getName().hashCode();
    }

    @Override
    public String toString()
    {
        return name;
    }
    
    
    
    
    protected Document createDocument() throws ParserConfigurationException
    {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);
        DocumentBuilder xmlBuilder = factory.newDocumentBuilder();
        return xmlBuilder.newDocument();
    }
    protected Document createDocument(HTTPMessage input) throws ParserConfigurationException, IOException, SAXException
    {
        return createDocument(input.getContent());
    }
    protected Document createDocument(byte[] content) throws ParserConfigurationException, IOException, SAXException
    {
        return new ExtendableByteBuffer()
                .write(content)
                .toXML();
    }
    
    
    
    
    
    
    
    protected String getHostPath(String path, String host)
    {
        return "http://" + (host.replace("/", "") + path.replace("\\", "/")).trim().replace("//", "/").replace(" ", "%20");
    }
    
    
    private final Map<FileSystemPath, IResource> openedResources;
    /*
    protected String getPath(String path, HTTPEnvironment environment)
    {
        return path.replace("/", "\\").trim();
    }
    private IResource getResource_(String path, HTTPEnvRequest environment)
    {
        return environment.getResourceFromPath(getPath(path, environment));
    }*/
    private IResource getNonBufferedResource(FileSystemPath path, HTTPEnvRequest environment)
    {
        return environment.getSettings()
                .getFileManager()
                .getResourceFromPath(path, environment);
    }
    protected IResource getResource(FileSystemPath path, HTTPEnvRequest environment)
    {
        if(environment.getSettings().getUseResourceBuffer())
        {
            if(openedResources.containsKey(path))
                return openedResources.get(path);
            else
            {
                IResource rs = getNonBufferedResource(path, environment);
                openedResources.put(path, rs);
                return rs;
            }
        }
        else
            return getNonBufferedResource(path, environment);
    }
    protected IResource getResource(String path, HTTPEnvRequest environment)
    {
        return getResource(environment.getSettings()
                .getFileSystemPathManager()
                .createFromString(path)
                , environment);
    }
    protected void closeResource(String path, HTTPEnvRequest environment)
    {
        closeResource(environment.getSettings()
                .getFileSystemPathManager()
                .createFromString(path)
                , environment);
    }
    protected void closeResource(FileSystemPath path, HTTPEnvRequest environment)
    {
        if(environment.getSettings().getUseResourceBuffer())
            openedResources.remove(path);
    }
}
 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 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 Lock
{
    public Lock(LockKind lockKind)
    {
        this.lockKind = lockKind;
        
        // generate dead line
        Calendar cal = Calendar.getInstance();
        cal.setTime(Date.from(Instant.now()));
        cal.add(Calendar.SECOND, lockKind.getTimeout());
        deadlineTime = cal.getTime().toInstant();
    }
    public Lock(LockKind lockKind, String owner)
    {
        this(lockKind);
        this.owner = owner;
    }
    
    private final LockKind lockKind;
    public LockKind getLockKind()
    {
        return lockKind;
    }
    
    private final Instant deadlineTime;
    public boolean expired()
    {
        return deadlineTime.compareTo(Instant.now()) >= 0;
    }
    
    private String owner = null;
    public String getOwner()
    {
        return owner;
    }

    private String uuid = null;
    public String getUUID()
    {
        if(uuid == null)
            uuid = generateUUID();
        return uuid;
    }
    
    
    // <editor-fold defaultstate="collapsed" desc="UUID generator">
    private static Random rnd = null;
    private static Random getRnd()
    {
        if(rnd == null)
            rnd = new Random();
        return rnd;
    }
    private static String expand(String str, int nbChars)
    {
        while(str.length() < nbChars)
            str = "0" + str;
        return str;
    }
    private static String node = null;
    private static String getNode()
    {
        if(node == null)
        {
            long n = getRnd().nextLong();
            node = expand(Integer.toHexString((int)((n >> 32)&0xFFFF)), 4) + expand(Integer.toHexString((int)((n)&0xFFFF)), 8);
        }
        return node;
    }
    private synchronized static String generateUUID()
    {
        
        long timestamp = new Date().getTime();
        int rndNumber = getRnd().nextInt(0x3FFF) + 0x8000;
        return "urn:uuid:" +
                // time_low
                expand(Integer.toHexString((int)(timestamp&0xFFFFFFFF)), 8) + 
                // time_mid
               '-' + expand(Integer.toHexString((int)((timestamp >> 32)&0xFFFF)), 4) +
                // time_hi_and_version
               '-' + expand(Integer.toHexString((int)((timestamp >> (32+16))&0x0FFF) + 0x1000), 4) +
                // clock_seq_hi_and_reserved
               '-' + expand(Integer.toHexString((rndNumber >> 16)&0xFF), 2) +
                // clock_seq_low
               expand(Integer.toHexString(rndNumber&0xFF), 2) +
                // node
               '-' + getNode();
    }
    // </editor-fold>

    @Override
    public int hashCode()
    {
        return getUUID().hashCode();
    }
}
 public class LockKind
{
    public enum LockType
    {
        write
    }
    public enum LockScope
    {
        Shared,
        Exclusive
    }

    public LockKind(LockScope scope, LockType type)
    {
        this.scope = scope;
        this.type = type;
        this.timeout = 60; // 1 minute
    }
    public LockKind(LockScope scope, LockType type, int timeout)
    {
        this.scope = scope;
        this.type = type;
        this.timeout = timeout;
    }
    
    private final int timeout;
    public int getTimeout()
    {
        return timeout;
    }
    
    private final LockScope scope;
    public LockScope getScope()
    {
        return scope;
    }

    private final LockType type;
    public LockType getType()
    {
        return type;
    }

    @Override
    public int hashCode()
    {
        return scope.hashCode() * 31 + scope.hashCode();
    }

    @Override
    public boolean equals(Object obj)
    {
        if(obj == null)
            return false;
            
        if(getClass() != obj.getClass())
            return false;
        
        return this.hashCode() == ((LockKind)obj).hashCode();
    }
}
 public enum LockScope
{
    Shared,
    Exclusive
}
 public enum LockType
{
    write
}
 public class NodeListWrap implements Iterator<Node>
{
    private NodeListWrap(NodeList list)
    {
        this.list = list;
        this.maxIndex = list.getLength();
        this.index = 0;
    }
    private final NodeList list;
    private final int maxIndex;
    private int index;
    
    
    public static Stream<Node> getStream(NodeList list)
    {
        return StreamSupport.stream(
                Spliterators.spliteratorUnknownSize(new NodeListWrap(list), Spliterator.NONNULL),
                false);
    }
    
    public static byte[] getBytes(Document doc)
    {
        try
        {
            TransformerFactory transformerFactory = TransformerFactory.newInstance();
            Transformer transformer = transformerFactory.newTransformer();
            DOMSource source = new DOMSource(doc);
            ByteArrayOutputStream boas = new ByteArrayOutputStream();
            StreamResult result = new StreamResult(boas);

            transformer.transform(source, result);

            return boas.toByteArray();
        }
        catch (TransformerException ex)
        {
            return new byte[0];
        }
    }
    
    

    @Override
    public boolean hasNext()
    {
        return index < maxIndex;
    }

    @Override
    public Node next()
    {
        return list.item(index++);
    }
}
 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 enum ResourceType
{
    File,
    Directory
}

import http.FileSystemPath;
import http.server.HTTPCommand;
import http.server.message.HTTPMessage;
import http.server.message.HTTPResponse;
import java.io.IOException;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import webdav.server.resource.IResource;
import webdav.server.resource.Lock;
import webdav.server.resource.LockKind;
import webdav.server.resource.LockKind.LockScope;
import webdav.server.resource.LockKind.LockType;
import webdav.server.tools.NodeListWrap;
import http.server.exceptions.NotFoundException;
import http.server.exceptions.UserRequiredException;
import http.server.message.HTTPEnvRequest;
import org.w3c.dom.Node;
import webdav.server.resource.ResourceType;

package webdav.server.commands;


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);
