blob: 695282f0039074d9a3cc833bb67637801fb19812 [file] [log] [blame]
// Copyright 2017 The Bazel Authors. All rights reserved.
//
// 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.google.devtools.build.lib.vfs;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
/**
* Abstract base class for {@link PathFragment} instances that will be allocated when Blaze is run
* on a Windows platform.
*/
abstract class WindowsPathFragment extends PathFragment {
static final Helper HELPER = new Helper();
protected final char driveLetter;
protected WindowsPathFragment(char driveLetter, String[] segments) {
super(segments);
this.driveLetter = driveLetter;
}
@Override
public String windowsVolume() {
return (driveLetter != '\0') ? driveLetter + ":" : "";
}
@Override
public char getDriveLetter() {
return driveLetter;
}
@Override
protected int computeHashCode() {
int h = 0;
for (String segment : segments) {
int segmentHash = segment.toLowerCase().hashCode();
h = h * 31 + segmentHash;
}
return h;
}
private static class Helper extends PathFragment.Helper {
private static final char SEPARATOR_CHAR = '/';
// TODO(laszlocsomor): Lots of internal PathFragment operations, e.g. getPathString, use the
// primary separator char and do not use this.
private static final char EXTRA_SEPARATOR_CHAR = '\\';
private static boolean isDriveLetter(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
@Override
PathFragment create(String path) {
char driveLetter =
path.length() >= 2 && path.charAt(1) == ':' && isDriveLetter(path.charAt(0))
? Character.toUpperCase(path.charAt(0))
: '\0';
if (driveLetter != '\0') {
path = path.substring(2);
// TODO(bazel-team): Decide what to do about non-absolute paths with a volume name, e.g.
// C:x.
}
boolean isAbsolute = path.length() > 0 && isSeparator(path.charAt(0));
return isAbsolute
? new AbsoluteWindowsPathFragment(driveLetter, segment(path, 1))
: new RelativeWindowsPathFragment(driveLetter, segment(path, 0));
}
@Override
PathFragment createAlreadyInterned(char driveLetter, boolean isAbsolute, String[] segments) {
return isAbsolute
? new AbsoluteWindowsPathFragment(driveLetter, segments)
: new RelativeWindowsPathFragment(driveLetter, segments);
}
@Override
char getPrimarySeparatorChar() {
return SEPARATOR_CHAR;
}
@Override
boolean isSeparator(char c) {
return c == SEPARATOR_CHAR || c == EXTRA_SEPARATOR_CHAR;
}
@Override
boolean containsSeparatorChar(String path) {
// TODO(laszlocsomor): This is inefficient.
return path.indexOf(SEPARATOR_CHAR) != -1 || path.indexOf(EXTRA_SEPARATOR_CHAR) != -1;
}
@Override
boolean segmentsEqual(int length, String[] segments1, int offset1, String[] segments2) {
if ((segments1.length - offset1) < length || segments2.length < length) {
return false;
}
for (int i = 0; i < length; ++i) {
String seg1 = segments1[i + offset1];
String seg2 = segments2[i];
if ((seg1 == null) != (seg2 == null)) {
return false;
}
if (seg1 == null) {
continue;
}
// TODO(laszlocsomor): The calls to String#toLowerCase are inefficient and potentially
// repeated too. Also, why not use String#equalsIgnoreCase.
seg1 = seg1.toLowerCase();
seg2 = seg2.toLowerCase();
if (!seg1.equals(seg2)) {
return false;
}
}
return true;
}
@Override
protected int compare(PathFragment pathFragment1, PathFragment pathFragment2) {
if (pathFragment1.isAbsolute() != pathFragment2.isAbsolute()) {
return pathFragment1.isAbsolute() ? -1 : 1;
}
int cmp = Character.compare(pathFragment1.getDriveLetter(), pathFragment2.getDriveLetter());
if (cmp != 0) {
return cmp;
}
String[] segments1 = pathFragment1.segments();
String[] segments2 = pathFragment2.segments();
int len1 = segments1.length;
int len2 = segments2.length;
int n = Math.min(len1, len2);
for (int i = 0; i < n; i++) {
String seg1 = segments1[i].toLowerCase();
String seg2 = segments2[i].toLowerCase();
cmp = seg1.compareTo(seg2);
if (cmp != 0) {
return cmp;
}
}
return len1 - len2;
}
}
private static final class AbsoluteWindowsPathFragment extends WindowsPathFragment {
private AbsoluteWindowsPathFragment(char driveLetter, String[] segments) {
super(driveLetter, segments);
}
@Override
public boolean isAbsolute() {
return true;
}
@Override
protected int computeHashCode() {
int h = Boolean.TRUE.hashCode();
h = h * 31 + super.computeHashCode();
h = h * 31 + Character.valueOf(getDriveLetter()).hashCode();
return h;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof AbsoluteWindowsPathFragment)) {
return false;
}
if (this == other) {
return true;
}
AbsoluteWindowsPathFragment otherAbsoluteWindowsPathFragment =
(AbsoluteWindowsPathFragment) other;
return this.driveLetter == otherAbsoluteWindowsPathFragment.driveLetter
&& HELPER.segmentsEqual(this.segments, otherAbsoluteWindowsPathFragment.segments);
}
// Java serialization looks for the presence of this method in the concrete class. It is not
// inherited from the parent class.
@Override
protected Object writeReplace() {
return super.writeReplace();
}
// Java serialization looks for the presence of this method in the concrete class. It is not
// inherited from the parent class.
@Override
protected void readObject(ObjectInputStream stream) throws InvalidObjectException {
super.readObject(stream);
}
}
private static final class RelativeWindowsPathFragment extends WindowsPathFragment {
private RelativeWindowsPathFragment(char driveLetter, String[] segments) {
super(driveLetter, segments);
}
@Override
public boolean isAbsolute() {
return false;
}
@Override
protected int computeHashCode() {
int h = Boolean.FALSE.hashCode();
h = h * 31 + super.computeHashCode();
if (!isEmpty()) {
h = h * 31 + Character.valueOf(getDriveLetter()).hashCode();
}
return h;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof RelativeWindowsPathFragment)) {
return false;
}
if (this == other) {
return true;
}
RelativeWindowsPathFragment otherRelativeWindowsPathFragment =
(RelativeWindowsPathFragment) other;
return isEmpty() && otherRelativeWindowsPathFragment.isEmpty()
? true
: this.driveLetter == otherRelativeWindowsPathFragment.driveLetter
&& HELPER.segmentsEqual(this.segments, otherRelativeWindowsPathFragment.segments);
}
private boolean isEmpty() {
return segmentCount() == 0;
}
// Java serialization looks for the presence of this method in the concrete class. It is not
// inherited from the parent class.
@Override
protected Object writeReplace() {
return super.writeReplace();
}
// Java serialization looks for the presence of this method in the concrete class. It is not
// inherited from the parent class.
@Override
protected void readObject(ObjectInputStream stream) throws InvalidObjectException {
super.readObject(stream);
}
}
}