Path: blob/master/src/java.base/share/classes/jdk/internal/jrtfs/JrtPath.java
41159 views
/*1* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/24package jdk.internal.jrtfs;2526import java.io.File;27import java.io.IOError;28import java.io.IOException;29import java.io.InputStream;30import java.io.OutputStream;31import java.net.URI;32import java.net.URISyntaxException;33import java.nio.channels.FileChannel;34import java.nio.channels.SeekableByteChannel;35import java.nio.file.*;36import java.nio.file.DirectoryStream.Filter;37import java.nio.file.attribute.BasicFileAttributes;38import java.nio.file.attribute.BasicFileAttributeView;39import java.nio.file.attribute.FileAttribute;40import java.nio.file.attribute.FileTime;41import java.util.Iterator;42import java.util.Map;43import java.util.NoSuchElementException;44import java.util.Objects;45import java.util.Set;46import static java.nio.file.StandardOpenOption.*;47import static java.nio.file.StandardCopyOption.*;4849/**50* Base class for Path implementation of jrt file systems.51*52* @implNote This class needs to maintain JDK 8 source compatibility.53*54* It is used internally in the JDK to implement jimage/jrtfs access,55* but also compiled and delivered as part of the jrtfs.jar to support access56* to the jimage file provided by the shipped JDK by tools running on JDK 8.57*/58final class JrtPath implements Path {5960final JrtFileSystem jrtfs;61private final String path;62private volatile int[] offsets;6364JrtPath(JrtFileSystem jrtfs, String path) {65this.jrtfs = jrtfs;66this.path = normalize(path);67this.resolved = null;68}6970JrtPath(JrtFileSystem jrtfs, String path, boolean normalized) {71this.jrtfs = jrtfs;72this.path = normalized ? path : normalize(path);73this.resolved = null;74}7576final String getName() {77return path;78}7980@Override81public final JrtPath getRoot() {82if (this.isAbsolute()) {83return jrtfs.getRootPath();84} else {85return null;86}87}8889@Override90public final JrtPath getFileName() {91if (path.isEmpty())92return this;93if (path.length() == 1 && path.charAt(0) == '/')94return null;95int off = path.lastIndexOf('/');96if (off == -1)97return this;98return new JrtPath(jrtfs, path.substring(off + 1), true);99}100101@Override102public final JrtPath getParent() {103initOffsets();104int count = offsets.length;105if (count == 0) { // no elements so no parent106return null;107}108int off = offsets[count - 1] - 1;109if (off <= 0) { // parent is root only (may be null)110return getRoot();111}112return new JrtPath(jrtfs, path.substring(0, off));113}114115@Override116public final int getNameCount() {117initOffsets();118return offsets.length;119}120121@Override122public final JrtPath getName(int index) {123initOffsets();124if (index < 0 || index >= offsets.length) {125throw new IllegalArgumentException("index: " +126index + ", offsets length: " + offsets.length);127}128int begin = offsets[index];129int end;130if (index == (offsets.length - 1)) {131end = path.length();132} else {133end = offsets[index + 1];134}135return new JrtPath(jrtfs, path.substring(begin, end));136}137138@Override139public final JrtPath subpath(int beginIndex, int endIndex) {140initOffsets();141if (beginIndex < 0 || endIndex > offsets.length ||142beginIndex >= endIndex) {143throw new IllegalArgumentException(144"beginIndex: " + beginIndex + ", endIndex: " + endIndex +145", offsets length: " + offsets.length);146}147// starting/ending offsets148int begin = offsets[beginIndex];149int end;150if (endIndex == offsets.length) {151end = path.length();152} else {153end = offsets[endIndex];154}155return new JrtPath(jrtfs, path.substring(begin, end));156}157158@Override159public final JrtPath toRealPath(LinkOption... options) throws IOException {160return jrtfs.toRealPath(this, options);161}162163@Override164public final JrtPath toAbsolutePath() {165if (isAbsolute())166return this;167return new JrtPath(jrtfs, "/" + path, true);168}169170@Override171public final URI toUri() {172String p = toAbsolutePath().path;173if (!p.startsWith("/modules") || p.contains("..")) {174throw new IOError(new RuntimeException(p + " cannot be represented as URI"));175}176177p = p.substring("/modules".length());178if (p.isEmpty()) {179p = "/";180}181return toUri(p);182}183184private boolean equalsNameAt(JrtPath other, int index) {185int mbegin = offsets[index];186int mlen;187if (index == (offsets.length - 1)) {188mlen = path.length() - mbegin;189} else {190mlen = offsets[index + 1] - mbegin - 1;191}192int obegin = other.offsets[index];193int olen;194if (index == (other.offsets.length - 1)) {195olen = other.path.length() - obegin;196} else {197olen = other.offsets[index + 1] - obegin - 1;198}199if (mlen != olen) {200return false;201}202int n = 0;203while (n < mlen) {204if (path.charAt(mbegin + n) != other.path.charAt(obegin + n)) {205return false;206}207n++;208}209return true;210}211212@Override213public final JrtPath relativize(Path other) {214final JrtPath o = checkPath(other);215if (o.equals(this)) {216return new JrtPath(jrtfs, "", true);217}218if (path.isEmpty()) {219return o;220}221if (jrtfs != o.jrtfs || isAbsolute() != o.isAbsolute()) {222throw new IllegalArgumentException(223"Incorrect filesystem or path: " + other);224}225final String tp = this.path;226final String op = o.path;227if (op.startsWith(tp)) { // fast path228int off = tp.length();229if (op.charAt(off - 1) == '/')230return new JrtPath(jrtfs, op.substring(off), true);231if (op.charAt(off) == '/')232return new JrtPath(jrtfs, op.substring(off + 1), true);233}234int mc = this.getNameCount();235int oc = o.getNameCount();236int n = Math.min(mc, oc);237int i = 0;238while (i < n) {239if (!equalsNameAt(o, i)) {240break;241}242i++;243}244int dotdots = mc - i;245int len = dotdots * 3 - 1;246if (i < oc) {247len += (o.path.length() - o.offsets[i] + 1);248}249StringBuilder sb = new StringBuilder(len);250while (dotdots > 0) {251sb.append("..");252if (sb.length() < len) { // no tailing slash at the end253sb.append('/');254}255dotdots--;256}257if (i < oc) {258sb.append(o.path, o.offsets[i], o.path.length());259}260return new JrtPath(jrtfs, sb.toString(), true);261}262263@Override264public JrtFileSystem getFileSystem() {265return jrtfs;266}267268@Override269public final boolean isAbsolute() {270return !path.isEmpty() && path.charAt(0) == '/';271}272273@Override274public final JrtPath resolve(Path other) {275final JrtPath o = checkPath(other);276if (this.path.isEmpty() || o.isAbsolute()) {277return o;278}279if (o.path.isEmpty()) {280return this;281}282StringBuilder sb = new StringBuilder(path.length() + o.path.length() + 1);283sb.append(path);284if (path.charAt(path.length() - 1) != '/')285sb.append('/');286sb.append(o.path);287return new JrtPath(jrtfs, sb.toString(), true);288}289290@Override291public final Path resolveSibling(Path other) {292Objects.requireNonNull(other, "other");293Path parent = getParent();294return (parent == null) ? other : parent.resolve(other);295}296297@Override298public final boolean startsWith(Path other) {299if (!(Objects.requireNonNull(other) instanceof JrtPath))300return false;301final JrtPath o = (JrtPath)other;302final String tp = this.path;303final String op = o.path;304if (isAbsolute() != o.isAbsolute() || !tp.startsWith(op)) {305return false;306}307int off = op.length();308if (off == 0) {309return tp.isEmpty();310}311// check match is on name boundary312return tp.length() == off || tp.charAt(off) == '/' ||313off == 0 || op.charAt(off - 1) == '/';314}315316@Override317public final boolean endsWith(Path other) {318if (!(Objects.requireNonNull(other) instanceof JrtPath))319return false;320final JrtPath o = (JrtPath)other;321final JrtPath t = this;322int olast = o.path.length() - 1;323if (olast > 0 && o.path.charAt(olast) == '/') {324olast--;325}326int last = t.path.length() - 1;327if (last > 0 && t.path.charAt(last) == '/') {328last--;329}330if (olast == -1) { // o.path.length == 0331return last == -1;332}333if ((o.isAbsolute() && (!t.isAbsolute() || olast != last))334|| last < olast) {335return false;336}337for (; olast >= 0; olast--, last--) {338if (o.path.charAt(olast) != t.path.charAt(last)) {339return false;340}341}342return o.path.charAt(olast + 1) == '/' ||343last == -1 || t.path.charAt(last) == '/';344}345346@Override347public final JrtPath resolve(String other) {348return resolve(getFileSystem().getPath(other));349}350351@Override352public final Path resolveSibling(String other) {353return resolveSibling(getFileSystem().getPath(other));354}355356@Override357public final boolean startsWith(String other) {358return startsWith(getFileSystem().getPath(other));359}360361@Override362public final boolean endsWith(String other) {363return endsWith(getFileSystem().getPath(other));364}365366@Override367public final JrtPath normalize() {368String res = getResolved();369if (res == path) { // no change370return this;371}372return new JrtPath(jrtfs, res, true);373}374375private JrtPath checkPath(Path path) {376Objects.requireNonNull(path);377if (!(path instanceof JrtPath))378throw new ProviderMismatchException("path class: " +379path.getClass());380return (JrtPath) path;381}382383// create offset list if not already created384private void initOffsets() {385if (this.offsets == null) {386int len = path.length();387// count names388int count = 0;389int off = 0;390while (off < len) {391char c = path.charAt(off++);392if (c != '/') {393count++;394off = path.indexOf('/', off);395if (off == -1)396break;397}398}399// populate offsets400int[] offsets = new int[count];401count = 0;402off = 0;403while (off < len) {404char c = path.charAt(off);405if (c == '/') {406off++;407} else {408offsets[count++] = off++;409off = path.indexOf('/', off);410if (off == -1)411break;412}413}414this.offsets = offsets;415}416}417418private volatile String resolved;419420final String getResolvedPath() {421String r = resolved;422if (r == null) {423if (isAbsolute()) {424r = getResolved();425} else {426r = toAbsolutePath().getResolvedPath();427}428resolved = r;429}430return r;431}432433// removes redundant slashs, replace "\" to separator "/"434// and check for invalid characters435private static String normalize(String path) {436int len = path.length();437if (len == 0) {438return path;439}440char prevC = 0;441for (int i = 0; i < len; i++) {442char c = path.charAt(i);443if (c == '\\' || c == '\u0000') {444return normalize(path, i);445}446if (c == '/' && prevC == '/') {447return normalize(path, i - 1);448}449prevC = c;450}451if (prevC == '/' && len > 1) {452return path.substring(0, len - 1);453}454return path;455}456457private static String normalize(String path, int off) {458int len = path.length();459StringBuilder to = new StringBuilder(len);460to.append(path, 0, off);461char prevC = 0;462while (off < len) {463char c = path.charAt(off++);464if (c == '\\') {465c = '/';466}467if (c == '/' && prevC == '/') {468continue;469}470if (c == '\u0000') {471throw new InvalidPathException(path,472"Path: NUL character not allowed");473}474to.append(c);475prevC = c;476}477len = to.length();478if (len > 1 && to.charAt(len - 1) == '/') {479to.deleteCharAt(len - 1);480}481return to.toString();482}483484// Remove DotSlash(./) and resolve DotDot (..) components485private String getResolved() {486int length = path.length();487if (length == 0 || (path.indexOf("./") == -1 && path.charAt(length - 1) != '.')) {488return path;489} else {490return resolvePath();491}492}493494private String resolvePath() {495int length = path.length();496char[] to = new char[length];497int nc = getNameCount();498int[] lastM = new int[nc];499int lastMOff = -1;500int m = 0;501for (int i = 0; i < nc; i++) {502int n = offsets[i];503int len = (i == offsets.length - 1) ? length - n504: offsets[i + 1] - n - 1;505if (len == 1 && path.charAt(n) == '.') {506if (m == 0 && path.charAt(0) == '/') // absolute path507to[m++] = '/';508continue;509}510if (len == 2 && path.charAt(n) == '.' && path.charAt(n + 1) == '.') {511if (lastMOff >= 0) {512m = lastM[lastMOff--]; // retreat513continue;514}515if (path.charAt(0) == '/') { // "/../xyz" skip516if (m == 0)517to[m++] = '/';518} else { // "../xyz" -> "../xyz"519if (m != 0 && to[m-1] != '/')520to[m++] = '/';521while (len-- > 0)522to[m++] = path.charAt(n++);523}524continue;525}526if (m == 0 && path.charAt(0) == '/' || // absolute path527m != 0 && to[m-1] != '/') { // not the first name528to[m++] = '/';529}530lastM[++lastMOff] = m;531while (len-- > 0)532to[m++] = path.charAt(n++);533}534if (m > 1 && to[m - 1] == '/')535m--;536return (m == to.length) ? new String(to) : new String(to, 0, m);537}538539@Override540public final String toString() {541return path;542}543544@Override545public final int hashCode() {546return path.hashCode();547}548549@Override550public final boolean equals(Object obj) {551return obj instanceof JrtPath &&552this.path.equals(((JrtPath) obj).path);553}554555@Override556public final int compareTo(Path other) {557final JrtPath o = checkPath(other);558return path.compareTo(o.path);559}560561@Override562public final WatchKey register(563WatchService watcher,564WatchEvent.Kind<?>[] events,565WatchEvent.Modifier... modifiers) {566Objects.requireNonNull(watcher, "watcher");567Objects.requireNonNull(events, "events");568Objects.requireNonNull(modifiers, "modifiers");569throw new UnsupportedOperationException();570}571572@Override573public final WatchKey register(WatchService watcher, WatchEvent.Kind<?>... events) {574return register(watcher, events, new WatchEvent.Modifier[0]);575}576577@Override578public final File toFile() {579throw new UnsupportedOperationException();580}581582@Override583public final Iterator<Path> iterator() {584return new Iterator<Path>() {585private int i = 0;586587@Override588public boolean hasNext() {589return (i < getNameCount());590}591592@Override593public Path next() {594if (i < getNameCount()) {595Path result = getName(i);596i++;597return result;598} else {599throw new NoSuchElementException();600}601}602603@Override604public void remove() {605throw new ReadOnlyFileSystemException();606}607};608}609610// Helpers for JrtFileSystemProvider and JrtFileSystem611612final JrtPath readSymbolicLink() throws IOException {613if (!jrtfs.isLink(this)) {614throw new IOException("not a symbolic link");615}616return jrtfs.resolveLink(this);617}618619final boolean isHidden() {620return false;621}622623final void createDirectory(FileAttribute<?>... attrs)624throws IOException {625jrtfs.createDirectory(this, attrs);626}627628final InputStream newInputStream(OpenOption... options) throws IOException {629if (options.length > 0) {630for (OpenOption opt : options) {631if (opt != READ) {632throw new UnsupportedOperationException("'" + opt + "' not allowed");633}634}635}636return jrtfs.newInputStream(this);637}638639final DirectoryStream<Path> newDirectoryStream(Filter<? super Path> filter)640throws IOException {641return new JrtDirectoryStream(this, filter);642}643644final void delete() throws IOException {645jrtfs.deleteFile(this, true);646}647648final void deleteIfExists() throws IOException {649jrtfs.deleteFile(this, false);650}651652final JrtFileAttributes getAttributes(LinkOption... options) throws IOException {653JrtFileAttributes zfas = jrtfs.getFileAttributes(this, options);654if (zfas == null) {655throw new NoSuchFileException(toString());656}657return zfas;658}659660final void setAttribute(String attribute, Object value, LinkOption... options)661throws IOException {662JrtFileAttributeView.setAttribute(this, attribute, value);663}664665final Map<String, Object> readAttributes(String attributes, LinkOption... options)666throws IOException {667return JrtFileAttributeView.readAttributes(this, attributes, options);668}669670final void setTimes(FileTime mtime, FileTime atime, FileTime ctime)671throws IOException {672jrtfs.setTimes(this, mtime, atime, ctime);673}674675final FileStore getFileStore() throws IOException {676// each JrtFileSystem only has one root (as requested for now)677if (exists()) {678return jrtfs.getFileStore(this);679}680throw new NoSuchFileException(path);681}682683final boolean isSameFile(Path other) throws IOException {684if (this == other || this.equals(other)) {685return true;686}687if (other == null || this.getFileSystem() != other.getFileSystem()) {688return false;689}690this.checkAccess();691JrtPath o = (JrtPath) other;692o.checkAccess();693return this.getResolvedPath().equals(o.getResolvedPath()) ||694jrtfs.isSameFile(this, o);695}696697final SeekableByteChannel newByteChannel(Set<? extends OpenOption> options,698FileAttribute<?>... attrs)699throws IOException700{701return jrtfs.newByteChannel(this, options, attrs);702}703704final FileChannel newFileChannel(Set<? extends OpenOption> options,705FileAttribute<?>... attrs)706throws IOException {707return jrtfs.newFileChannel(this, options, attrs);708}709710final void checkAccess(AccessMode... modes) throws IOException {711if (modes.length == 0) { // check if the path exists712jrtfs.checkNode(this); // no need to follow link. the "link" node713// is built from real node under "/module"714} else {715boolean w = false;716for (AccessMode mode : modes) {717switch (mode) {718case READ:719break;720case WRITE:721w = true;722break;723case EXECUTE:724throw new AccessDeniedException(toString());725default:726throw new UnsupportedOperationException();727}728}729jrtfs.checkNode(this);730if (w && jrtfs.isReadOnly()) {731throw new AccessDeniedException(toString());732}733}734}735736final boolean exists() {737try {738return jrtfs.exists(this);739} catch (IOException x) {}740return false;741}742743final OutputStream newOutputStream(OpenOption... options) throws IOException {744if (options.length == 0) {745return jrtfs.newOutputStream(this, CREATE_NEW, WRITE);746}747return jrtfs.newOutputStream(this, options);748}749750final void move(JrtPath target, CopyOption... options) throws IOException {751if (this.jrtfs == target.jrtfs) {752jrtfs.copyFile(true, this, target, options);753} else {754copyToTarget(target, options);755delete();756}757}758759final void copy(JrtPath target, CopyOption... options) throws IOException {760if (this.jrtfs == target.jrtfs) {761jrtfs.copyFile(false, this, target, options);762} else {763copyToTarget(target, options);764}765}766767private void copyToTarget(JrtPath target, CopyOption... options)768throws IOException {769boolean replaceExisting = false;770boolean copyAttrs = false;771for (CopyOption opt : options) {772if (opt == REPLACE_EXISTING) {773replaceExisting = true;774} else if (opt == COPY_ATTRIBUTES) {775copyAttrs = true;776}777}778// attributes of source file779BasicFileAttributes jrtfas = getAttributes();780// check if target exists781boolean exists;782if (replaceExisting) {783try {784target.deleteIfExists();785exists = false;786} catch (DirectoryNotEmptyException x) {787exists = true;788}789} else {790exists = target.exists();791}792if (exists) {793throw new FileAlreadyExistsException(target.toString());794}795if (jrtfas.isDirectory()) {796// create directory or file797target.createDirectory();798} else {799try (InputStream is = jrtfs.newInputStream(this);800OutputStream os = target.newOutputStream()) {801byte[] buf = new byte[8192];802int n;803while ((n = is.read(buf)) != -1) {804os.write(buf, 0, n);805}806}807}808if (copyAttrs) {809BasicFileAttributeView view =810Files.getFileAttributeView(target, BasicFileAttributeView.class);811try {812view.setTimes(jrtfas.lastModifiedTime(),813jrtfas.lastAccessTime(),814jrtfas.creationTime());815} catch (IOException x) {816try {817target.delete(); // rollback?818} catch (IOException ignore) {}819throw x;820}821}822}823824// adopted from sun.nio.fs.UnixUriUtils825private static URI toUri(String str) {826char[] path = str.toCharArray();827assert path[0] == '/';828StringBuilder sb = new StringBuilder();829sb.append(path[0]);830for (int i = 1; i < path.length; i++) {831char c = (char)(path[i] & 0xff);832if (match(c, L_PATH, H_PATH)) {833sb.append(c);834} else {835sb.append('%');836sb.append(hexDigits[(c >> 4) & 0x0f]);837sb.append(hexDigits[(c) & 0x0f]);838}839}840841try {842return new URI("jrt:" + sb.toString());843} catch (URISyntaxException x) {844throw new AssertionError(x); // should not happen845}846}847848// The following is copied from java.net.URI849850// Compute the low-order mask for the characters in the given string851private static long lowMask(String chars) {852int n = chars.length();853long m = 0;854for (int i = 0; i < n; i++) {855char c = chars.charAt(i);856if (c < 64)857m |= (1L << c);858}859return m;860}861862// Compute the high-order mask for the characters in the given string863private static long highMask(String chars) {864int n = chars.length();865long m = 0;866for (int i = 0; i < n; i++) {867char c = chars.charAt(i);868if ((c >= 64) && (c < 128))869m |= (1L << (c - 64));870}871return m;872}873874// Compute a low-order mask for the characters875// between first and last, inclusive876private static long lowMask(char first, char last) {877long m = 0;878int f = Math.max(Math.min(first, 63), 0);879int l = Math.max(Math.min(last, 63), 0);880for (int i = f; i <= l; i++)881m |= 1L << i;882return m;883}884885// Compute a high-order mask for the characters886// between first and last, inclusive887private static long highMask(char first, char last) {888long m = 0;889int f = Math.max(Math.min(first, 127), 64) - 64;890int l = Math.max(Math.min(last, 127), 64) - 64;891for (int i = f; i <= l; i++)892m |= 1L << i;893return m;894}895896// Tell whether the given character is permitted by the given mask pair897private static boolean match(char c, long lowMask, long highMask) {898if (c < 64)899return ((1L << c) & lowMask) != 0;900if (c < 128)901return ((1L << (c - 64)) & highMask) != 0;902return false;903}904905// digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" |906// "8" | "9"907private static final long L_DIGIT = lowMask('0', '9');908private static final long H_DIGIT = 0L;909910// upalpha = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" |911// "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" |912// "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z"913private static final long L_UPALPHA = 0L;914private static final long H_UPALPHA = highMask('A', 'Z');915916// lowalpha = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" |917// "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" |918// "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z"919private static final long L_LOWALPHA = 0L;920private static final long H_LOWALPHA = highMask('a', 'z');921922// alpha = lowalpha | upalpha923private static final long L_ALPHA = L_LOWALPHA | L_UPALPHA;924private static final long H_ALPHA = H_LOWALPHA | H_UPALPHA;925926// alphanum = alpha | digit927private static final long L_ALPHANUM = L_DIGIT | L_ALPHA;928private static final long H_ALPHANUM = H_DIGIT | H_ALPHA;929930// mark = "-" | "_" | "." | "!" | "~" | "*" | "'" |931// "(" | ")"932private static final long L_MARK = lowMask("-_.!~*'()");933private static final long H_MARK = highMask("-_.!~*'()");934935// unreserved = alphanum | mark936private static final long L_UNRESERVED = L_ALPHANUM | L_MARK;937private static final long H_UNRESERVED = H_ALPHANUM | H_MARK;938939// pchar = unreserved | escaped |940// ":" | "@" | "&" | "=" | "+" | "$" | ","941private static final long L_PCHAR942= L_UNRESERVED | lowMask(":@&=+$,");943private static final long H_PCHAR944= H_UNRESERVED | highMask(":@&=+$,");945946// All valid path characters947private static final long L_PATH = L_PCHAR | lowMask(";/");948private static final long H_PATH = H_PCHAR | highMask(";/");949950private static final char[] hexDigits = {951'0', '1', '2', '3', '4', '5', '6', '7',952'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'953};954}955956957