/*
* Copyright (C) 2011 4th Line GmbH, Switzerland
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.fourthline.lemma.anchor;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.MethodDoc;
import com.sun.javadoc.PackageDoc;
import com.sun.javadoc.SeeTag;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.logging.Logger;
/**
* Encapsulates parsing of citation anchor URIs.
* <p>
* Two addresses are <em>equal</em> if they have the same scheme and path, and (if not null) fragment.
* </p>
*
* @author Christian Bauer
*/
public class AnchorAddress {
final private static Logger log = Logger.getLogger(AnchorAddress.class.getName());
protected static final String PATTERN_SCHEME = "([a-zA-Z]+?)";
protected static final String PATTERN_PATH = "([ \\p{Alnum}\\./_-]+?)";
protected static final String PATTERN_FRAGMENT = "([\\p{Alnum}]+?(?:\\([\\p{Alnum},\\.\\[\\]<>\\s]*?\\))??)";
public static final Pattern PATTERN =
Pattern.compile("^"+PATTERN_SCHEME+Scheme.SEPARATOR+PATTERN_PATH+"(?:#"+PATTERN_FRAGMENT+")??$");
public static final String PATH_THIS = "this";
final private Scheme scheme;
final private String path;
final private String fragment;
public AnchorAddress(Scheme scheme, String path, String fragment) {
if (path == null || path.length() == 0) {
throw new IllegalArgumentException("Reference path can not be empty");
}
this.scheme = scheme;
this.path = path;
if (fragment != null && !fragment.endsWith(")")) fragment = fragment + "()"; // normalize
this.fragment = fragment;
}
public Scheme getScheme() {
return scheme;
}
public String getPath() {
return path;
}
public String getFragment() {
return fragment;
}
public static AnchorAddress valueOf(String string) {
if (string == null || string.length() == 0) return null;
Matcher m = PATTERN.matcher(string.trim());
if (!m.matches()) {
// If it doesn't then convert it to file:// address and try again
String fileString = toFileAddress(string);
log.finest("Address string does not contain a schema, using file schema: " + fileString);
m.reset(fileString);
if (!m.matches()) {
throw new IllegalArgumentException("Invalid reference, no pattern match of anchor: " + string);
}
}
return new AnchorAddress(Scheme.valueOf(m.group(1).toUpperCase()), m.group(2), m.group(3));
}
public static AnchorAddress valueOf(Scheme scheme, SeeTag tag) {
String reference = null;
if (tag.referencedMember() != null && tag.referencedMember().isMethod()) {
reference = tag.referencedClassName() + "#" +
tag.referencedMember().name() +
((MethodDoc) tag.referencedMember()).flatSignature();
} else if (tag.referencedClass() != null) {
reference = tag.referencedClassName();
} else if (tag.referencedPackage() != null) {
reference = tag.referencedPackage().name();
}
return reference != null
? valueOf(scheme.name().toLowerCase() + Scheme.SEPARATOR + reference)
: null;
}
public static AnchorAddress valueOf(Scheme scheme, PackageDoc packageDoc) {
return new AnchorAddress(
scheme,
packageDoc.name(),
null
);
}
public static AnchorAddress valueOf(Scheme scheme, ClassDoc classDoc, String fragment) {
return new AnchorAddress(
scheme,
classDoc.qualifiedTypeName(),
fragment
);
}
public static AnchorAddress valueOf(Scheme scheme, MethodDoc methodDoc) {
return new AnchorAddress(
scheme,
methodDoc.containingClass().qualifiedTypeName(),
methodDoc.name() + methodDoc.flatSignature()
);
}
/**
* Converts a simple file path into a <code>file://</code> URI string.
*
* @param string A file path, e.g. "/my/file.txt".
* @return A URI string, e.g. "file://my/file.txt";
*/
public static String toFileAddress(String string) {
if (string.startsWith(".")) throw new IllegalArgumentException("Reference can't start with a period '.'");
// Always remove leading and trailing slashes
if (string.startsWith("/")) string = string.substring(1);
if (string.endsWith("/")) string = string.substring(0, string.length() - 1);
return Scheme.FILE + Scheme.SEPARATOR + string;
}
/**
* Generates an XSD:id typed String that can be used in XML documents as identifier attribute value.
*
* @return An XSD:id compatible string representation of this URI.
*/
public String toIdentifierString() {
String schemeString = getScheme().name().toLowerCase();
String pathString = getPath();
String fragmentString = (getFragment() != null ? "#" + getFragment() : "");
return (schemeString + "." + pathString + fragmentString)
.replaceAll("\\s", "")
.replaceAll("#|,", ".")
.replaceAll("\\[|\\]", "-")
.replaceAll("\\(|\\)", ".")
.replaceAll("[^a-zA-Z0-9-._]", "_")
.replaceAll("__", "_");
}
/*
public boolean isMatching(SeeTag tag) {
// Is the referenced class/member/package of this tag the same as the target of this citation?
if (tag.referencedPackage() != null) {
if (!getPath().equals(tag.referencedPackage().name()) || getFragment() != null) {
return false;
}
}
if (tag.referencedClass() != null) {
if (!getPath().equals(tag.referencedClassName())) {
return false;
}
}
if (tag.referencedMember() != null) {
if (getFragment() == null || !getFragment().equals(tag.referencedMemberName())) {
return false;
}
}
return true;
}
*/
/**
* @return The complement of the <code>valueOf(String)</code> operation.
*/
@Override
public String toString() {
return getScheme().name().toLowerCase() + Scheme.SEPARATOR + getPath() + (getFragment() != null ? "#" + getFragment() : "");
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AnchorAddress that = (AnchorAddress) o;
if (fragment != null ? !fragment.equals(that.fragment) : that.fragment != null) return false;
if (!path.equals(that.path)) return false;
if (scheme != that.scheme) return false;
return true;
}
@Override
public int hashCode() {
int result = scheme.hashCode();
result = 31 * result + path.hashCode();
result = 31 * result + (fragment != null ? fragment.hashCode() : 0);
return result;
}
}