package org.concord.otrunk.xml;
import java.net.URL;
public class URLUtil
{
public static String getRelativeURL(URL context, URL url)
{
String urlStr = url.toExternalForm();
// there is also the URI relativize method that could be used here
// but it won't do .. notation
if(context.getProtocol() == null ||
!context.getProtocol().equals(url.getProtocol())){
return urlStr;
}
// As far as I can tell the authority includes the host and the port
if(context.getAuthority() != null && !context.getAuthority().equals("") &&
!context.getAuthority().equals(url.getAuthority())){
return urlStr;
}
String contextPath = context.getPath();
String urlPath = url.getPath();
// The "split" method used below creates an empty string
// at the beginning if the url starts with
String strippedContextPath = contextPath;
String strippedUrlPath = urlPath;
if(contextPath.startsWith("/")){
strippedContextPath = contextPath.substring(1);
if(urlPath.startsWith("/")){
strippedUrlPath = urlPath.substring(1);
} else {
// The later code makes the assumption that if codebase path starts with
// a / then the url path will too. So if that isn't true then we
// throw an IllegalStateException until we figure out what to do here.
throw new IllegalStateException("contextPath and urlPath are not consistent " +
"context: "+ context + " url: " + url);
}
}
// If the contextPath doesn't end with a slash then it is considered a file
// so url is relative to the directory of the context.
String codebasePath = strippedContextPath;
if(!strippedContextPath.endsWith("/")){
int slashIndex = strippedContextPath.lastIndexOf('/');
if(slashIndex < 0){
// no slash so the context file is at the root of the authority
// If we are here it is because the two urls have the same authority
// so we can return a relative url that is the file without the leading slash
// if we set the codebasePath to "/" then the split method will return
// a empty array which will have the desired effect below
codebasePath = "/";
} else if(slashIndex == 0){
// there is a slash at the beginning of the path, what does that mean?
throw new IllegalStateException("Last slash at the beginning of the path: " + context);
} else {
codebasePath = strippedContextPath.substring(0, slashIndex);
}
}
String[] codebasePathSegments = codebasePath.split("/");
String[] urlPathSegment = strippedUrlPath.split("/");
int i = 0;
for(; i<codebasePathSegments.length; i++){
if(i >= urlPathSegment.length){
break;
}
if(!codebasePathSegments[i].equals(urlPathSegment[i])){
break;
}
}
// Handle the case where they don't match at all
// if the both start with / then there will be one matching
// segment "". So we special case that one.
int matchingSegments = i;
if(matchingSegments == 0){
String relativeUrl = url.getFile();
if(url.getRef() != null){
relativeUrl += "#" + url.getRef();
}
// if the codebase is the root of the authority then we should strip off the
// leading slash of the relative url
if(codebasePathSegments.length == 0 && relativeUrl.startsWith("/")){
relativeUrl = relativeUrl.substring(1);
}
return relativeUrl;
}
int codebaseNonMatchingSegments = codebasePathSegments.length - (matchingSegments);
String parentString = "";
for(int j=0; j<codebaseNonMatchingSegments; j++){
parentString += "../";
}
String relativeUrl = parentString;
int urlNonMatchingSegments = urlPathSegment.length - (matchingSegments);
for(int j=0; j<urlNonMatchingSegments; j++){
relativeUrl += urlPathSegment[matchingSegments + j] + "/";
}
if(urlPath.endsWith("/") && !relativeUrl.endsWith("/")){
relativeUrl += "/";
} else if(!urlPath.endsWith("/") && relativeUrl.endsWith("/")){
relativeUrl = relativeUrl.substring(0, relativeUrl.length()-1);
}
if(url.getQuery() != null){
relativeUrl += "?" + url.getQuery();
}
if(url.getRef() != null){
relativeUrl += "#" + url.getRef();
}
return relativeUrl;
}
}