diff options
author | 2017-04-24 17:41:23 +0200 | |
---|---|---|
committer | 2017-04-24 18:00:38 +0200 | |
commit | aac13242c1e2bb6d1870e1284795bd3ca370984c (patch) | |
tree | f0cbeda6d8806f2d0560f9941da7fb52ce95dc80 /src/main/java/com/google/devtools/build/lib/vfs/WindowsPathFragment.java | |
parent | 3d596d63f883fff56001ed7b2e5cf51dba45f082 (diff) |
Make PathFragment an abstract class.
There are now four concrete implementations: RelativeUnixPathFragment, AbsoluteUnixPathFragment, RelativeWindowsPathFragment, AbsoluteWindowsPathFragment.
Goals:
-Reduce memory usage of PathFragment on non-Windows platforms while maintaining existing semantics.
-Make a few simple performance improvements along the way.
-Add TODOs for a few more simple performance improvements.
-Open the way for reducing code complexity of PathFragment. All of the Windows-specific stuff ought not complicate the code so much.
Non goals:
-Make the entire codebase as pretty as possible wrt PathFragment & Windows.
-Make PathFragment usage more sane in general (e.g. change semantics to ban coexistence of Windows and Unix PathFragments).
-Optimize PathFragment as much as possible wrt memory or even in any other dimensions (e.g. gc churn, cpu).
To elaborate, the primary motivation is per-instance memory usage of PathFragment on Unix platforms:
Before this change
------------------
+UseCompressedOops --> 32 bytes per instance
-UseCompressedOops --> 40 bytes per instance
After this change
------------------
+UseCompressedOops --> 24 bytes per instance
-UseCompressedOops --> 32 bytes per instance
Since Bazel can retain lots of PathFragments, the memory savings of this CL are fairly large.
RELNOTES: None
PiperOrigin-RevId: 154052905
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/vfs/WindowsPathFragment.java')
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/vfs/WindowsPathFragment.java | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/WindowsPathFragment.java b/src/main/java/com/google/devtools/build/lib/vfs/WindowsPathFragment.java new file mode 100644 index 0000000000..4f94535acf --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/vfs/WindowsPathFragment.java @@ -0,0 +1,252 @@ +// 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); + } + } +} |