All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.jackrabbit.vault.packaging.PackageId Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
/*
 * 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.vault.packaging;

import org.apache.jackrabbit.util.XMLChar;
import org.jetbrains.annotations.NotNull;
import org.apache.jackrabbit.util.Text;

/**
 * {@code PackageId} provides the basic metrics for identifying a package.
 * A package id consists of a group, a name and an optional version.
 * The group is a relative path, eg: "company/project/subgroup", the name and the version
 * can be of any format.
 * 

* The string representation is {@code :[:]}. */ public class PackageId implements Comparable { /** * The root path of the packages storage location. * @deprecated As of 3.1.42, the storage location is implementation details. */ @Deprecated public static final String ETC_PACKAGES = "/etc/packages"; /** * The root path prefix of the packages storage location. * @deprecated As of 3.1.42, the storage location is implementation details. */ @Deprecated public static final String ETC_PACKAGES_PREFIX = "/etc/packages/"; public static final PackageId[] EMPTY = new PackageId[0]; private final String group; private final String name; private final Version version; private final String str; private final boolean fromPath; /** * Creates a new package id * @param path path of the package * * @deprecated As of 3.1.42, the storage location is implementation details. */ @Deprecated public PackageId(String path) { fromPath = true; path = path.trim(); int idx = path.lastIndexOf('.'); if (idx > 0) { String ext = path.substring(idx); if (".zip".equalsIgnoreCase(ext) || ".jar".equalsIgnoreCase(ext)) { path = path.substring(0, idx); } } idx = path.lastIndexOf('/'); String name; if (idx < 0) { name = path; this.group = ""; } else { name = path.substring(idx + 1); String grp = path.substring(0, idx); if (grp.equals(ETC_PACKAGES)) { grp = ""; } else if (grp.startsWith(ETC_PACKAGES_PREFIX)) { grp = grp.substring(ETC_PACKAGES_PREFIX.length()); } else if (grp.startsWith("/")) { grp = grp.substring(1); } this.group = grp; } // check if name contains a version String[] segs = Text.explode(name, '-'); int i=segs.length-1; while (i>0) { try { // accept numbers < 1000 (hotfix case) if (Integer.parseInt(segs[i]) >= 1000) { break; } } catch (NumberFormatException e) { // ignore } // check if starts with a letter' if (Character.isJavaIdentifierStart(segs[i].charAt(0))) { // then need a digit if (segs[i].length() == 1 || !Character.isDigit(segs[i].charAt(1)) && !"SNAPSHOT".equals(segs[i])) { break; } } i--; } if (i == segs.length-1) { this.name = name; version = Version.EMPTY; } else { StringBuilder str = new StringBuilder(); for (int j = 0; j<= i; j++) { if (j > 0) { str.append('-'); } str.append(segs[j]); } this.name = str.toString(); str.setLength(0); for (int j = i+1; j i+1) { str.append('-'); } str.append(segs[j]); } this.version = Version.create(str.toString()); } this.str = getString(group, this.name, version); } /** * Creates a new package id * @param path path of the package * @param version version of the package * * @deprecated As of 3.1.42, the storage location is implementation details. */ @Deprecated public PackageId(String path, String version) { this(path, Version.create(version)); } /** * Creates a new package id * @param path path of the package * @param version version of the package * * @deprecated As of 3.1.42, the storage location is implementation details. */ @Deprecated public PackageId(String path, Version version) { fromPath = true; path = path.trim(); int idx = path.lastIndexOf('.'); if (idx > 0) { String ext = path.substring(idx); if (".zip".equalsIgnoreCase(ext) || ".jar".equalsIgnoreCase(ext)) { path = path.substring(0, idx); } } if (version != null && path.endsWith('-'+version.toString())) { path = path.substring(0, path.length() - version.toString().length() - 1); } idx = path.lastIndexOf('/'); if (idx < 0) { this.name = path; this.group = ""; } else { this.name = path.substring(idx + 1); String grp = path.substring(0, idx); if (grp.equals(ETC_PACKAGES)) { grp = ""; } else if (grp.startsWith(ETC_PACKAGES_PREFIX)) { grp = grp.substring(ETC_PACKAGES_PREFIX.length()); } else if (grp.startsWith("/")) { grp = grp.substring(1); } this.group = grp; } // sanitize version if (version == null || version.toString().length() == 0) { version = Version.EMPTY; } this.version = version; this.str = getString(group, name, version); } /** * Creates a new package id * @param group group id * @param name name * @param version version */ public PackageId(String group, String name, String version) { this(group, name, Version.create(version)); } /** * Creates a new package id * @param group group id * @param name name * @param version version */ public PackageId(String group, String name, Version version) { fromPath = false; // validate group if (group.equals(ETC_PACKAGES)) { group = ""; } else if (group.startsWith(ETC_PACKAGES_PREFIX)) { group = group.substring(ETC_PACKAGES_PREFIX.length()); } else if (group.startsWith("/")) { group = group.substring(1); } this.group = group; this.name = name; this.version = version == null ? Version.EMPTY : version; this.str = getString(this.group, name, this.version); } /** * Returns a package id from an id string in the format {@code :[:]}. If the given id is null or an * empty string, {@code null} is returned. * @param str the string * @return the package id */ public static PackageId fromString(String str) { if (str == null || str.length() == 0) { return null; } String[] segs = str.split(":"); if (segs.length == 1) { return new PackageId("", segs[0], ""); } else if (segs.length == 2) { return new PackageId(segs[0], segs[1], ""); } else { return new PackageId(segs[0], segs[1], segs[2]); } } /** * Returns an array of package id from strings. * @param str the strings * @return the array of package ids * @see #fromString(String) */ public static PackageId[] fromString(String ... str) { PackageId[] ret = new PackageId[str.length]; for (int i=0; i:[:]}. * @param packs the ids * @return the string */ public static String toString(PackageId ... packs) { String delim = ""; StringBuilder b = new StringBuilder(); for (PackageId pack: packs) { b.append(delim).append(pack); delim=","; } return b.toString(); } /** * Checks if this definition was constructed from a path, rather from a * group and name. * @return {@code true} if constructed from path. * * @since 2.2.26 * @deprecated As of 3.1.42, the storage location is implementation details. */ @Deprecated public boolean isFromPath() { return fromPath; } /** * Returns the path of this package. please note that since 2.3 this also * includes the version, but never the extension (.zip). * * @return the path of this package * @since 2.2 * @deprecated As of 3.1.42, the storage location is implementation details. */ @Deprecated public String getInstallationPath() { StringBuilder b = new StringBuilder(ETC_PACKAGES_PREFIX); if (group.length() > 0) { b.append(group); b.append("/"); } b.append(name); if (version.toString().length() > 0) { b.append("-").append(version); } return b.toString(); } /** * Returns the group of this package * @return the group. * @since 2.2 */ public String getGroup() { return group; } /** * Returns the name of this package (usually this is the last segment of the path). * @return the name of this package. */ public @NotNull String getName() { return name; } /** * Returns the version of this package or and empty string if n/a. * @return the version of this package * @since 2.0 */ public @NotNull String getVersionString() { return version.toString(); } /** * Returns a download name in the form * {@code name [ "-" version ] ".zip"} * @return the download name * @since 2.0 */ public String getDownloadName() { StringBuilder str = new StringBuilder(name); if (version.toString().length() > 0) { str.append("-").append(version); } str.append(".zip"); return str.toString(); } /** * Returns the version of this package or {@code Version.EMPTY} if not set. * @return the version of this package */ public @NotNull Version getVersion() { return version; } /** * Returns a string representation of this id in the format {@code :[:]}. */ @Override public String toString() { return str; } @Override public boolean equals(Object o) { return this == o || o instanceof PackageId && str.equals(o.toString()); } @Override public int hashCode() { return str.hashCode(); } /** * {@inheritDoc} * * Compares this id with the given one. */ public int compareTo(PackageId o) { int comp = group.compareTo(o.getGroup()); if (comp != 0) { return comp; } comp = name.compareTo(o.getName()); if (comp != 0) { return comp; } return version.compareTo(o.getVersion()); } /** * Internally get the string representation, colon separated. * @param group group name * @param name name * @param version version * @return string version */ private static String getString(String group, String name, Version version) { return getString(group, name, version == null ? "" : version.toString()); } /** * Internally get the string representation, colon separated. * @param group group name * @param name name * @param version version * @return string version */ private static String getString(String group, String name, String version) { StringBuilder b = new StringBuilder(); b.append(group).append(':'); b.append(name); if (version.length() > 0) { b.append(':').append(version); } return b.toString(); } /** * Checks if this package id is valid in respect to JCR names. * @return {@code true} if the names are valid */ public boolean isValid() { return PackageId.isValid(group, name, version == null ? null : version.toString()); } /** * Checks if the package id is valid in respect to JCR names. * @param group the package group name * @param name the package name * @param version the (optional) version * @return {@code true} if the names are valid */ public static boolean isValid(String group, String name, String version) { try { assertValidJcrName(name); if (version != null && !version.isEmpty()) { assertValidJcrName(version); } for (String groupSegment: Text.explode(group, '/')) { assertValidJcrName(groupSegment); } return true; } catch (IllegalArgumentException e) { return false; } } // the code below is copied from org.apache.jackrabbit.spi.commons.conversion.NameParser // 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_URI_START = 4; private static final int STATE_URI = 5; /** * Parses the {@code jcrName} (either qualified or expanded) and validates it. * @throws java.lang.IllegalArgumentException if the name is not valid */ private static void assertValidJcrName(String jcrName) throws IllegalArgumentException { // trivial check int len = jcrName == null ? 0 : jcrName.length(); if (len == 0) { throw new IllegalArgumentException("empty name"); } if (".".equals(jcrName) || "..".equals(jcrName)) { throw new IllegalArgumentException(jcrName); } // parse the name String prefix; int nameStart = 0; int state = STATE_PREFIX_START; boolean trailingSpaces = false; for (int i = 0; i < len; i++) { char c = jcrName.charAt(i); if (c == ':') { if (state == STATE_PREFIX_START) { throw new IllegalArgumentException("Prefix must not be empty"); } else if (state == STATE_PREFIX) { if (trailingSpaces) { throw new IllegalArgumentException("Trailing spaces not allowed"); } prefix = jcrName.substring(0, i); if (!XMLChar.isValidNCName(prefix)) { throw new IllegalArgumentException("Invalid name prefix: "+ prefix); } state = STATE_NAME_START; } else if (state == STATE_URI) { // ignore -> validation of uri later on. } else { throw new IllegalArgumentException("'" + c + "' not allowed in name"); } trailingSpaces = false; } else if (c == ' ') { if (state == STATE_PREFIX_START || state == STATE_NAME_START) { throw new IllegalArgumentException("'" + c + "' not valid name start"); } trailingSpaces = true; } else if (Character.isWhitespace(c) || c == '[' || c == ']' || c == '*' || c == '|') { throw new IllegalArgumentException("'" + c + "' not allowed in name"); } else if (c == '/') { if (state == STATE_URI_START) { state = STATE_URI; } else if (state != STATE_URI) { throw new IllegalArgumentException("'" + c + "' not allowed in name"); } trailingSpaces = false; } else if (c == '{') { if (state == STATE_PREFIX_START) { state = STATE_URI_START; } else if (state == STATE_URI_START || state == STATE_URI) { // second '{' in the uri-part -> no valid expanded jcr-name. // therefore reset the nameStart and change state. state = STATE_NAME; nameStart = 0; } else if (state == STATE_NAME_START) { state = STATE_NAME; nameStart = i; } trailingSpaces = false; } else if (c == '}') { if (state == STATE_URI_START || state == STATE_URI) { String tmp = jcrName.substring(1, i); if (tmp.length() == 0 || tmp.indexOf(':') != -1) { // The leading "{...}" part is empty or contains // a colon, so we treat it as a valid namespace URI. // More detailed validity checks (is it well formed, // registered, etc.) are not needed here. state = STATE_NAME_START; } else if ("internal".equals(tmp)) { // As a special Jackrabbit backwards compatibility // feature, support {internal} as a valid URI prefix state = STATE_NAME_START; } else if (tmp.indexOf('/') == -1) { // The leading "{...}" contains neither a colon nor // a slash, so we can interpret it as a a part of a // normal local name. state = STATE_NAME; nameStart = 0; } else { throw new IllegalArgumentException( "The URI prefix of the name " + jcrName + " is neither a valid URI nor a valid part" + " of a local name."); } } else if (state == STATE_PREFIX_START) { state = STATE_PREFIX; // prefix start -> validation later on will fail. } else if (state == STATE_NAME_START) { state = STATE_NAME; nameStart = i; } trailingSpaces = false; } else { if (state == STATE_PREFIX_START) { state = STATE_PREFIX; // prefix start } else if (state == STATE_NAME_START) { state = STATE_NAME; nameStart = i; } else if (state == STATE_URI_START) { state = STATE_URI; } trailingSpaces = false; } } // take care of qualified jcrNames starting with '{' that are not having // a terminating '}' -> make sure there are no illegal characters present. if (state == STATE_URI && (jcrName.indexOf(':') > -1 || jcrName.indexOf('/') > -1)) { throw new IllegalArgumentException("Local name may not contain ':' nor '/'"); } if (nameStart == len || state == STATE_NAME_START) { throw new IllegalArgumentException("Local name must not be empty"); } if (trailingSpaces) { throw new IllegalArgumentException("Trailing spaces not allowed"); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy