org.apache.jackrabbit.spi.commons.conversion.PathParser Maven / Gradle / Ivy
Show all versions of aem-sdk-api Show documentation
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.jackrabbit.spi.commons.conversion;
import org.apache.jackrabbit.spi.Path;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.PathFactory;
import org.apache.jackrabbit.spi.commons.name.PathBuilder;
import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl;
import javax.jcr.NamespaceException;
/**
* PathParser
formats a {@link Path} using a
* {@link NameResolver} and a {@link PathFactory}.
*/
public class PathParser {
// constants for parser
private static final int STATE_PREFIX_START = 0;
private static final int STATE_PREFIX = 1;
private static final int STATE_NAME_START = 2;
private static final int STATE_NAME = 3;
private static final int STATE_INDEX = 4;
private static final int STATE_INDEX_END = 5;
private static final int STATE_DOT = 6;
private static final int STATE_DOTDOT = 7;
private static final int STATE_IDENTIFIER = 8;
private static final int STATE_URI = 9;
private static final int STATE_URI_END = 10;
private static final char EOF = (char) -1;
/**
* Parses jcrPath
into a Path
object using
* resolver
to convert prefixes into namespace URIs. If
* resolver is null
this method only checks the format of the
* passed String and returns null
.
*
* @param jcrPath the jcr path.
* @param resolver the namespace resolver.
* @param factory PathFactory
to be used.
* @return A path object.
* @throws MalformedPathException If the jcrPath
is malformed.
* @throws IllegalNameException if any of the jcrNames is malformed.
* @throws NamespaceException If an unresolvable prefix is encountered.
*/
public static Path parse(String jcrPath, NameResolver resolver, PathFactory factory)
throws MalformedPathException, IllegalNameException, NamespaceException {
return parse(null, jcrPath, resolver, factory);
}
/**
* Parses jcrPath
into a Path
object using
* resolver
to convert prefixes into namespace URIs. If the
* specified jcrPath
is an identifier based absolute path
* beginning with an identifier segment the specified
* IdentifierResolver
will be used to resolve it to an
* absolute path.
*
* If namResolver
is null
or if identifierResolver
* is null
and the path starts with an identifier segment, this
* method only checks the format of the string and returns null
.
*
* @param jcrPath the jcr path.
* @param nameResolver the namespace resolver.
* @param identifierResolver the resolver to validate any trailing identifier
* segment and resolve to an absolute path.
* @param factory
* @return A path object.
* @throws MalformedPathException If the jcrPath
is malformed.
* @throws IllegalNameException if any of the jcrNames is malformed.
* @throws NamespaceException If an unresolvable prefix is encountered.
* @since JCR 2.0
*/
public static Path parse(String jcrPath, NameResolver nameResolver,
IdentifierResolver identifierResolver, PathFactory factory)
throws MalformedPathException, IllegalNameException, NamespaceException {
return parse(null, jcrPath, nameResolver, identifierResolver, factory);
}
/**
* Parses jcrPath
into a Path
object using
* resolver
to convert prefixes into namespace URIs. If the
* specified jcrPath
is an identifier based absolute path
* beginning with an identifier segment the specified
* IdentifierResolver
will be used to resolve it to an
* absolute path.
*
* If namResolver
is null
or if identifierResolver
* is null
and the path starts with an identifier segment, this
* method only checks the format of the string and returns null
.
*
* @param jcrPath the jcr path.
* @param nameResolver the namespace resolver.
* @param identifierResolver the resolver to validate any trailing identifier
* segment and resolve to an absolute path.
* @param factory
* @param normalizeIdentifier
* @return A path object.
* @throws MalformedPathException If the jcrPath
is malformed.
* @throws IllegalNameException if any of the jcrNames is malformed.
* @throws NamespaceException If an unresolvable prefix is encountered.
* @since JCR 2.0
*/
public static Path parse(String jcrPath, NameResolver nameResolver,
IdentifierResolver identifierResolver,
PathFactory factory, boolean normalizeIdentifier)
throws MalformedPathException, IllegalNameException, NamespaceException {
return parse(null, jcrPath, nameResolver, identifierResolver, factory, normalizeIdentifier);
}
/**
* Parses the given jcrPath
and returns a Path
. If
* parent
is not null
, it is prepended to the
* built path before it is returned. If resolver
is
* null
, this method only checks the format of the string and
* returns null
.
*
* @param parent the parent path
* @param jcrPath the JCR path
* @param resolver the namespace resolver to get prefixes for namespace
* URIs.
* @param factory
* @return the Path
object.
* @throws MalformedPathException If the jcrPath
is malformed.
* @throws IllegalNameException if any of the jcrNames is malformed.
* @throws NamespaceException If an unresolvable prefix is encountered.
*/
public static Path parse(Path parent, String jcrPath,
NameResolver resolver,
PathFactory factory) throws MalformedPathException, IllegalNameException, NamespaceException {
return parse(parent, jcrPath, resolver, null, factory);
}
/**
* Parses the given jcrPath
and returns a Path
. If
* parent
is not null
, it is prepended to the
* built path before it is returned. If the specified jcrPath
* is an identifier based absolute path beginning with an identifier segment
* the given identifierResolver
will be used to resolve it to an
* absolute path.
*
* If nameResolver
is null
or if identifierResolver
* is null
and the path starts with an identifier segment, this
* method only checks the format of the string and returns null
.
*
* @param parent the parent path.
* @param jcrPath the jcr path.
* @param nameResolver the namespace resolver.
* @param identifierResolver the resolver to validate any trailing identifier
* segment and resolve it to an absolute path.
* @param factory The path factory.
* @return the Path
object.
* @throws MalformedPathException
* @throws IllegalNameException
* @throws NamespaceException
*/
public static Path parse(Path parent, String jcrPath, NameResolver nameResolver,
IdentifierResolver identifierResolver, PathFactory factory)
throws MalformedPathException, IllegalNameException, NamespaceException {
return parse(parent, jcrPath, nameResolver, identifierResolver, factory, true);
}
/**
* Parses the given jcrPath
and returns a Path
. If
* parent
is not null
, it is prepended to the
* built path before it is returned. If the specified jcrPath
* is an identifier based absolute path beginning with an identifier segment
* the given identifierResolver
will be used to resolve it to an
* absolute path.
*
* If nameResolver
is null
or if identifierResolver
* is null
and the path starts with an identifier segment, this
* method only checks the format of the string and returns null
.
*
* @param parent the parent path.
* @param jcrPath the jcr path.
* @param nameResolver the namespace resolver.
* @param identifierResolver the resolver to validate any trailing identifier
* segment and resolve it to an absolute path.
* @param factory The path factory.
* @param normalizeIdentifier
* @return the Path
object.
* @throws MalformedPathException
* @throws IllegalNameException
* @throws NamespaceException
*/
private static Path parse(Path parent, String jcrPath, NameResolver nameResolver,
IdentifierResolver identifierResolver, PathFactory factory,
boolean normalizeIdentifier)
throws MalformedPathException, IllegalNameException, NamespaceException {
// check for length
int len = jcrPath == null ? 0 : jcrPath.length();
// shortcut
if (len == 1 && jcrPath.charAt(0) == '/') {
return factory.getRootPath();
}
if (len == 0) {
throw new MalformedPathException("empty path");
}
// check if absolute path
PathBuilder builder = new PathBuilder(factory);
int pos = 0;
if (jcrPath.charAt(0) == '/') {
if (parent != null) {
throw new MalformedPathException("'" + jcrPath + "' is not a relative path.");
}
builder.addRoot();
pos++;
}
// add master if present
if (parent != null) {
builder.addAll(parent.getElements());
}
// parse the path
int state;
if (jcrPath.charAt(0) == '[') {
if (parent != null) {
throw new MalformedPathException("'" + jcrPath + "' is not a relative path.");
}
state = STATE_IDENTIFIER;
pos++;
} else {
state = STATE_PREFIX_START;
}
int lastPos = pos;
String name = null;
int index = Path.INDEX_UNDEFINED;
boolean wasSlash = false;
boolean checkFormat = (nameResolver == null);
while (pos <= len) {
char c = pos == len ? EOF : jcrPath.charAt(pos);
char rawCharacter = c;
pos++;
// special check for whitespace
if (c != ' ' && Character.isWhitespace(c)) {
c = '\t';
}
switch (c) {
case '/':
case EOF:
if (state == STATE_PREFIX_START && c != EOF) {
throw new MalformedPathException("'" + jcrPath + "' is not a valid path. double slash '//' not allowed.");
}
if (state == STATE_URI && c == EOF) {
// this handles the case where URI state was entered but the end of the segment was reached (JCR-3562)
state = STATE_URI_END;
}
if (state == STATE_PREFIX
|| state == STATE_NAME
|| state == STATE_INDEX_END
|| state == STATE_URI_END) {
// eof pathelement
if (name == null) {
if (wasSlash) {
throw new MalformedPathException("'" + jcrPath + "' is not a valid path: Trailing slashes not allowed in prefixes and names.");
}
name = jcrPath.substring(lastPos, pos - 1);
}
// only add element if resolver not null. otherwise this
// is just a check for valid format.
if (checkFormat) {
NameParser.checkFormat(name);
} else {
Name qName = nameResolver.getQName(name);
builder.addLast(qName, index);
}
state = STATE_PREFIX_START;
lastPos = pos;
name = null;
index = Path.INDEX_UNDEFINED;
} else if (state == STATE_IDENTIFIER) {
if (c == EOF) {
// eof identifier reached
if (jcrPath.charAt(pos - 2) != ']') {
throw new MalformedPathException("'" + jcrPath + "' is not a valid path: Unterminated identifier segment.");
}
String identifier = jcrPath.substring(lastPos, pos - 2);
if (checkFormat) {
if (identifierResolver != null) {
identifierResolver.checkFormat(identifier);
} // else ignore. TODO: rather throw?
} else if (identifierResolver == null) {
throw new MalformedPathException("'" + jcrPath + "' is not a valid path: Identifier segments are not supported.");
} else if (normalizeIdentifier) {
builder.addAll(identifierResolver.getPath(identifier).getElements());
} else {
identifierResolver.checkFormat(identifier);
builder.addLast(factory.createElement(identifier));
}
state = STATE_PREFIX_START;
lastPos = pos;
}
} else if (state == STATE_DOT) {
builder.addLast(factory.getCurrentElement());
lastPos = pos;
state = STATE_PREFIX_START;
} else if (state == STATE_DOTDOT) {
builder.addLast(factory.getParentElement());
lastPos = pos;
state = STATE_PREFIX_START;
} else if (state != STATE_URI
&& !(state == STATE_PREFIX_START && c == EOF)) { // ignore trailing slash
throw new MalformedPathException("'" + jcrPath + "' is not a valid path. '" + c + "' not a valid name character.");
}
break;
case '.':
if (state == STATE_PREFIX_START) {
state = STATE_DOT;
} else if (state == STATE_DOT) {
state = STATE_DOTDOT;
} else if (state == STATE_DOTDOT) {
state = STATE_PREFIX;
} else if (state == STATE_INDEX_END) {
throw new MalformedPathException("'" + jcrPath + "' is not a valid path. '" + c + "' not valid after index. '/' expected.");
}
break;
case ':':
if (state == STATE_PREFIX_START) {
throw new MalformedPathException("'" + jcrPath + "' is not a valid path. Prefix must not be empty");
} else if (state == STATE_PREFIX) {
if (wasSlash) {
throw new MalformedPathException("'" + jcrPath + "' is not a valid path: Trailing slashes not allowed in prefixes and names.");
}
state = STATE_NAME_START;
// don't reset the lastPos/pos since prefix+name are passed together to the NameResolver
} else if (state != STATE_IDENTIFIER && state != STATE_URI) {
throw new MalformedPathException("'" + jcrPath + "' is not a valid path. '" + c + "' not valid name character");
}
break;
case '[':
if (state == STATE_PREFIX || state == STATE_NAME) {
if (wasSlash) {
throw new MalformedPathException("'" + jcrPath + "' is not a valid path: Trailing slashes not allowed in prefixes and names.");
}
state = STATE_INDEX;
name = jcrPath.substring(lastPos, pos - 1);
lastPos = pos;
} else if (state != STATE_IDENTIFIER) {
throw new MalformedPathException("'" + jcrPath + "' is not a valid path. '" + c + "' not a valid name character.");
}
break;
case ']':
if (state == STATE_INDEX) {
try {
index = Integer.parseInt(jcrPath.substring(lastPos, pos - 1));
} catch (NumberFormatException e) {
throw new MalformedPathException("'" + jcrPath + "' is not a valid path. NumberFormatException in index: " + jcrPath.substring(lastPos, pos - 1));
}
if (index < Path.INDEX_DEFAULT) {
throw new MalformedPathException("'" + jcrPath + "' is not a valid path. Index number invalid: " + index);
}
state = STATE_INDEX_END;
} else if (state != STATE_IDENTIFIER) {
throw new MalformedPathException("'" + jcrPath + "' is not a valid path. '" + c + "' not a valid name character.");
}
break;
case ' ':
if (state == STATE_PREFIX_START || state == STATE_NAME_START) {
throw new MalformedPathException("'" + jcrPath + "' is not a valid path. '" + c + "' not valid name start");
} else if (state == STATE_INDEX_END) {
throw new MalformedPathException("'" + jcrPath + "' is not a valid path. '" + c + "' not valid after index. '/' expected.");
} else if (state == STATE_DOT || state == STATE_DOTDOT) {
state = STATE_PREFIX;
}
break;
case '\t':
if (state != STATE_IDENTIFIER) {
String message = String.format("'%s' is not a valid path. Whitespace other than SP (U+0020) not a allowed in a name, but U+%04x was found at position %d.",
jcrPath, (long) rawCharacter, pos - 1);
throw new MalformedPathException(message);
}
case '*':
case '|':
if (state != STATE_IDENTIFIER) {
throw new MalformedPathException("'" + jcrPath + "' is not a valid path. '" + c + "' not a valid name character.");
}
case '{':
if (state == STATE_PREFIX_START && lastPos == pos-1) {
// '{' marks the start of a uri enclosed in an expanded name
// instead of the usual namespace prefix, if it is
// located at the beginning of a new segment.
state = STATE_URI;
} else if (state == STATE_NAME_START || state == STATE_DOT || state == STATE_DOTDOT) {
// otherwise it's part of the local name
state = STATE_NAME;
}
break;
case '}':
if (state == STATE_URI) {
state = STATE_URI_END;
}
break;
default:
if (state == STATE_PREFIX_START || state == STATE_DOT || state == STATE_DOTDOT) {
state = STATE_PREFIX;
} else if (state == STATE_NAME_START) {
state = STATE_NAME;
} else if (state == STATE_INDEX_END) {
throw new MalformedPathException("'" + jcrPath + "' is not a valid path. '" + c + "' not valid after index. '/' expected.");
}
}
wasSlash = c == ' ';
}
if (checkFormat) {
// this was only for checking the format
return null;
} else {
return builder.getPath();
}
}
/**
* Check the format of the given jcr path. Note, the neither name nor
* namespace validation (resolution of prefix to URI) is performed and
* therefore will not be detected.
*
* @param jcrPath
* @throws MalformedPathException If the jcrPath
is malformed.
*/
public static void checkFormat(String jcrPath) throws MalformedPathException {
try {
// since no path is created -> use default factory
parse(jcrPath, null, null, PathFactoryImpl.getInstance());
} catch (NamespaceException e) {
// will never occur
} catch (IllegalNameException e) {
// will never occur
}
}
}