com.apicatalog.jsonld.http.link.LinkHeaderParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of titanium-json-ld Show documentation
Show all versions of titanium-json-ld Show documentation
A JSON-LD 1.1 Processor & API
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.apicatalog.jsonld.http.link;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.apicatalog.jsonld.http.HttpAlphabet;
import com.apicatalog.jsonld.http.media.MediaType;
import com.apicatalog.jsonld.uri.UriResolver;
/**
*
* @see Appendix B. Algorithms for Parsing Link Header Fields
*
*/
final class LinkHeaderParser {
private static final String REL = "rel";
private static final String ANCHOR = "anchor";
private static final String TYPE = "type";
private enum State { INIT, URI_REF, PARAMS, PARAM_NAME_BEGIN, PARAM_NAME, PARAM_NAME_END, PARAM_VALUE,
STRING_VALUE, LITERAL_VALUE, ESCAPE, UNEXPECTED }
private URI baseUri;
private final StringBuilder valueBuilder;
private List links;
private State state;
private URI targetUri;
private String attributeName;
private String attributeValue;
private Map> attributes;
public LinkHeaderParser(final URI baseUri) {
this.baseUri = baseUri;
this.valueBuilder = new StringBuilder();
}
public List parse(String httpLink) {
resetState(baseUri);
final char[] linkHeader = httpLink.toCharArray();
for (final char ch : linkHeader) {
switch (state) {
case INIT:
initParser(ch);
break;
case URI_REF:
parseTargetUri(ch);
break;
case PARAMS:
parseParameters(ch);
break;
case PARAM_NAME_BEGIN:
parseParamNameBegin(ch);
break;
case PARAM_NAME:
parseParamName(ch);
break;
case PARAM_NAME_END:
parseParamNameEnd(ch);
break;
case PARAM_VALUE:
parseParamValue(ch);
break;
case LITERAL_VALUE:
parseLiteral(ch);
break;
case STRING_VALUE:
parseString(ch);
break;
case ESCAPE:
escape(ch);
break;
default:
addParameter();
addLink();
return links;
}
}
return sweep();
}
private final List sweep() {
switch (state) {
case PARAM_NAME_BEGIN:
case PARAM_NAME:
case PARAM_NAME_END:
if (valueBuilder.length() > 0) {
attributeName = valueBuilder.toString().stripTrailing();
}
break;
case LITERAL_VALUE:
if (valueBuilder.length() > 0) {
attributeValue = valueBuilder.toString().stripTrailing();
}
break;
case UNEXPECTED:
return links;
default:
break;
}
addParameter();
addLink();
return links;
}
private final void addLink() {
if (targetUri != null) {
Set rel = Collections.emptySet();
URI context = null;
MediaType type = null;
if (attributes.containsKey(REL) && attributes.get(REL) != null) {
rel = new HashSet<>(Arrays.asList(attributes.get(REL).get(0).value().strip().split("[\\s\\t]+")));
attributes.remove(REL);
}
if (attributes.containsKey(ANCHOR) && attributes.get(ANCHOR) != null) {
context = URI.create(UriResolver.resolve(baseUri, attributes.get(ANCHOR).get(0).value().strip()));
attributes.remove(ANCHOR);
}
if (attributes.containsKey(TYPE) && attributes.get(TYPE) != null) {
type = MediaType.of(attributes.get(TYPE).get(0).value());
if (type != null) {
attributes.remove(TYPE);
}
}
links.add(new Link(context, targetUri, rel, type, new LinkAttributes(attributes)));
targetUri = null;
attributes = new LinkedHashMap<>();
}
}
private final void addParameter() {
if (attributeName != null) {
if (attributeValue != null) {
attributes
.computeIfAbsent(attributeName, l -> new ArrayList<>())
.add(new LinkAttribute(attributeName, attributeValue));
attributeValue = null;
} else {
attributes
.computeIfAbsent(attributeName, l -> new ArrayList<>())
.add(new LinkAttribute(attributeName));
}
attributeName = null;
}
}
private final void resetState(URI baseUri) {
this.baseUri = baseUri;
this.links = new ArrayList<>();
this.attributes = new LinkedHashMap<>();
this.state = State.INIT;
this.targetUri = null;
this.attributeName = null;
this.attributeValue = null;
}
private final void initParser(final char ch) {
// skip white-spaces
if (HttpAlphabet.WHITESPACE.test(ch)) {
return;
}
if (ch == '<') {
valueBuilder.setLength(0);
state = State.URI_REF;
return;
}
state = State.UNEXPECTED;
}
private final void parseTargetUri(final char ch) {
if (ch != '>') {
// skip leading white-spaces
if (valueBuilder.length() > 0 || HttpAlphabet.WHITESPACE.negate().test(ch)) {
valueBuilder.append(ch);
}
return;
}
targetUri = URI.create(UriResolver.resolve(baseUri, valueBuilder.toString().stripTrailing()));
state = State.PARAMS;
}
private final void parseParameters(final char ch) {
// skip leading white-spaces
if (HttpAlphabet.WHITESPACE.test(ch)) {
return;
}
// next link
if (ch == ',') {
addLink();
state = State.INIT;
return;
}
// next attribute
if (ch == ';') {
valueBuilder.setLength(0);
state = State.PARAM_NAME_BEGIN;
return;
}
addLink();
state = State.UNEXPECTED;
}
private final void parseParamNameBegin(final char ch) {
// skip leading white-spaces
if (HttpAlphabet.WHITESPACE.test(ch)) {
return;
}
if (HttpAlphabet.T_CHAR.test(ch)) {
valueBuilder.append(ch);
state = State.PARAM_NAME;
return;
}
addLink();
state = State.UNEXPECTED;
}
private final void parseParamName(final char ch) {
if (ch == '=') {
attributeName = valueBuilder.toString();
valueBuilder.setLength(0);
state = State.PARAM_VALUE;
return;
}
if (ch == ';') {
attributeName = valueBuilder.toString();
valueBuilder.setLength(0);
addParameter();
return;
}
if (ch == ',') {
attributeName = valueBuilder.toString();
addParameter();
addLink();
state = State.INIT;
return;
}
if (HttpAlphabet.T_CHAR.test(ch)) {
valueBuilder.append(ch);
return;
}
if (HttpAlphabet.WHITESPACE.test(ch)) {
attributeName = valueBuilder.toString();
valueBuilder.setLength(0);
state = State.PARAM_NAME_END;
return;
}
addLink();
state = State.UNEXPECTED;
}
private final void parseParamNameEnd(final char ch) {
if (HttpAlphabet.WHITESPACE.test(ch)) {
return;
}
if (ch == '=') {
state = State.PARAM_VALUE;
return;
}
if (ch == ';') {
addParameter();
state = State.PARAM_NAME_BEGIN;
return;
}
if (ch == ',') {
addParameter();
addLink();
state = State.INIT;
return;
}
addLink();
state = State.UNEXPECTED;
}
private final void parseParamValue(final char ch) {
if (HttpAlphabet.WHITESPACE.test(ch)) {
return;
}
if (ch == '"') {
state = State.STRING_VALUE;
return;
}
if (HttpAlphabet.T_CHAR.test(ch)) {
valueBuilder.append(ch);
state = State.LITERAL_VALUE;
return;
}
addLink();
state = State.UNEXPECTED;
}
private final void parseString(final char ch) {
if (ch == '"') {
attributeValue = valueBuilder.toString();
addParameter();
state = State.PARAMS;
return;
}
if (ch == '\\') {
state = State.ESCAPE;
return;
}
if (HttpAlphabet.QD_TEXT.test(ch)) {
valueBuilder.append(ch);
return;
}
addLink();
state = State.UNEXPECTED;
}
private final void parseLiteral(final char ch) {
if (ch == ';') {
attributeValue = valueBuilder.toString();
valueBuilder.setLength(0);
addParameter();
state = State.PARAM_NAME;
return;
} else if (ch == ',') {
attributeValue = valueBuilder.toString();
addParameter();
addLink();
state = State.INIT;
return;
}
if (HttpAlphabet.T_CHAR.test(ch)) {
valueBuilder.append(ch);
return;
}
if (HttpAlphabet.WHITESPACE.test(ch)) {
attributeValue = valueBuilder.toString();
addParameter();
state = State.PARAMS;
return;
}
addLink();
state = State.UNEXPECTED;
}
private final void escape(final char ch) {
if (ch == 't') {
valueBuilder.append('\t');
} else {
valueBuilder.append(ch);
}
state = State.STRING_VALUE;
}
}