// 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 = '\\'; @Override PathFragment create(String path) { // TODO(laszlocsomor): Character#isLetter returns true for some non ASCII characters. char driveLetter = path.length() >= 2 && path.charAt(1) == ':' && Character.isLetter(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); } } }