Webiyo

This page was generated using Webiyo. See its source code and unit tests.

org/webiyo/web/Link.java

package org.webiyo.web;

import org.w3c.dom.Document;
import org.webiyo.xml.AttributeSink;
import org.webiyo.xml.AttributeSource;
import org.webiyo.xml.Xml;
import org.webiyo.xml.dtds.Html;
import org.xml.sax.InputSource;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.regex.Pattern;

public class Link implements AttributeSource {

    private static final Pattern ABSOLUTE_URL = Pattern.compile("[a-zA-Z][a-zA-Z0-9+\\.-]*:.*");

    private final String protocol;
    private final String host;
    private final int port;
    private final List<String> pathItems;

    private Link(List<String> pathItems) {
        this(null, null, 0, pathItems);
    }

    public Link(String link) {
        if (ABSOLUTE_URL.matcher(link).matches()) {
            try {
                URL url = new URL(link);
                this.protocol = url.getProtocol();
                this.host = url.getHost();
                this.port = choosePort(protocol, url.getPort());
                this.pathItems = makePathItems(url);
            } catch (MalformedURLException e) {
                throw new RuntimeException("can't parse URL: " + link, e);
            }
        } else {
            if (link.startsWith("/")) throw new IllegalArgumentException("site-relative url's not done yet");
            this.protocol = null;
            this.host = null;
            this.port = 0;
            this.pathItems = makePathItems(link);
        }
    }

    public Link(URL url) {
        this.protocol = url.getProtocol();
        this.host = url.getHost();
        this.port = choosePort(protocol, url.getPort());
        this.pathItems = makePathItems(url);
    }

    public Link(File file) {
        this(toURL(file));
    }

    private Link(String protocol, String host, int port, List<String> pathItems) {
        this.protocol = protocol;
        this.host = host;
        this.port = port;
        this.pathItems = pathItems;
    }

    public Document fetchXml() throws IOException {
        if (!isAbsolute()) throw new UnsupportedOperationException("not an absolute link: " + toString());
        return Xml.parse(new InputSource(toString()));
    }

    public boolean isAbsolute() {
        return protocol != null;
    }

    public void sendAttributes(AttributeSink sink) throws IOException {
        sink.putAttribute(Html.HREF.getName(), toString());
    }

    public String getPath() {
        return makePath(pathItems);
    }

    public List<String> getPathItems() {
        return pathItems;
    }

    public Link getParent() {
        if (pathItems.size() == 0 || (pathItems.size() == 1 && pathItems.get(0).equals("/"))) return null;
        return new Link(protocol, host, port, pathItems.subList(0, pathItems.size() - 1));
    }

    public boolean isDir() {
        return pathItems.size() == 0 || pathItems.get(pathItems.size() - 1).endsWith("/");
    }

    public Link getDir() {
        return isDir() ? this : getParent();
    }

    public Link findCommonAncestor(Link other) {
        if (!equals(protocol, other.protocol)) return null;
        if (!equals(host, other.host)) return null;
        if (!equals(port, other.port)) return null;

        List<String> result = new ArrayList<String>();
        Iterator<String> it1 = getPathItems().iterator();
        Iterator<String> it2 = other.getPathItems().iterator();
        while (it1.hasNext() && it2.hasNext()) {
            String item1 = it1.next();
            String item2 = it2.next();
            if (!item1.equals(item2) || !item1.endsWith("/")) break;
            result.add(item1);
        }

        return new Link(protocol, host, port, result);
    }

    public Link makeRelativeTo(Link base) {
        Link ancestor = findCommonAncestor(base);
        if (ancestor == null) {
            throw new IllegalArgumentException("Base URL (" + base + ") and target URL (" + this + ") have no common ancestor");
        }

        int backtrackCount = base.getDir().getPathSize() - ancestor.getPathSize();

        List<String> result = new ArrayList<String>();
        result.addAll(Collections.nCopies(backtrackCount, "../"));
        result.addAll(pathItems.subList(ancestor.getPathSize(), getPathSize()));
        return new Link(result);
    }

    public String getProtocol() {
        return protocol;
    }

    public String getHost() {
        return host;
    }

    public int getPort() {
        return port;
    }

    public String toString() {
        return host != null ? makeAbsoluteLink() : getPath();
    }
    
    // end of public methods

    protected int getPathSize() {
        return getPathItems().size();
    }
    
    // end of protected methods

    private String makeAbsoluteLink() {
        return protocol + "://" + host + (port == 80 ? "" : (":" + port)) + getPath();
    }

    private static List<String> makePathItems(String path) {
        List<String> items;
        if (path.equals(".")) {
            items = Collections.emptyList();
        } else {
            items = Collections.unmodifiableList(split(path));
        }
        return items;
    }

    private static List<String> makePathItems(URL url) {
        String file = url.getFile();
        return file.length() == 0 ? Arrays.asList("/") : makePathItems(file);
    }

    private static int choosePort(String protocol, int specifiedPort) {
        if (specifiedPort == -1 && protocol.equals("http")) {
            return 80;
        } else {
            return specifiedPort;
        }
    }

    private static boolean equals(Object first, Object second) {
        return first == null ? second == null : first.equals(second);
    }

    private static String toURL(File file) {
        try {
            return file.toURL().toString();
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException("can't convert file to URL: " + file, e);
        }
    }

    private static String makePath(List<String> items) {
        if (items.size() == 0) {
            return ".";
        } else {
            StringBuilder result = new StringBuilder();
            for (String name : items) {
                result.append(name);
            }
            return result.toString();
        }
    }

    private static List<String> split(String path) {
        List<String> result = new ArrayList<String>();
        int here = 0;
        while (true) {
            int slashIndex = path.indexOf('/', here);
            if (slashIndex == -1) break;
            String item = path.substring(here, slashIndex + 1);
            if (item.equals("../")) {
                if (result.size() > 0 && !result.get(result.size() - 1).equals("../")) {
                    result.remove(result.size() - 1);
                } else {
                    result.add(item);
                }
            } else if (!item.equals("./")) {
                result.add(item);
            }
            here = slashIndex + 1;
        }

        String rest = path.substring(here);

        if (rest.equals(".")) {
            rest = "";
        }

        if (rest.length() > 0) {
            result.add(rest);
        }

        return result;
    }

}

SourceForge