Skip to content

Package: PathNormalization

PathNormalization

nameinstructionbranchcomplexitylinemethod
PathNormalization()
M: 7 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
equalsNormalized(Path, Path)
M: 0 C: 8
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
fixedPath(Path)
M: 6 C: 76
93%
M: 2 C: 10
83%
M: 2 C: 5
71%
M: 0 C: 19
100%
M: 0 C: 1
100%
lambda$fixedPath$0(Path, Path)
M: 0 C: 4
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
normalizedToNativeForm(String)
M: 2 C: 6
75%
M: 1 C: 1
50%
M: 1 C: 1
50%
M: 0 C: 1
100%
M: 0 C: 1
100%
static {...}
M: 12 C: 23
66%
M: 3 C: 1
25%
M: 3 C: 1
25%
M: 5 C: 7
58%
M: 0 C: 1
100%

Coverage

1: /*
2: * *********************************************************************************************************************
3: *
4: * blueMarine II: Semantic Media Centre
5: * http://tidalwave.it/projects/bluemarine2
6: *
7: * Copyright (C) 2015 - 2021 by Tidalwave s.a.s. (http://tidalwave.it)
8: *
9: * *********************************************************************************************************************
10: *
11: * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
12: * the License. You may obtain a copy of the License at
13: *
14: * http://www.apache.org/licenses/LICENSE-2.0
15: *
16: * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
17: * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
18: * specific language governing permissions and limitations under the License.
19: *
20: * *********************************************************************************************************************
21: *
22: * git clone https://bitbucket.org/tidalwave/bluemarine2-src
23: * git clone https://github.com/tidalwave-it/bluemarine2-src
24: *
25: * *********************************************************************************************************************
26: */
27: package it.tidalwave.bluemarine2.util;
28:
29: import javax.annotation.Nonnull;
30: import javax.annotation.Nullable;
31: import java.text.Normalizer;
32: import java.util.Objects;
33: import java.util.Optional;
34: import java.util.stream.Stream;
35: import java.io.IOException;
36: import java.nio.file.Files;
37: import java.nio.file.Path;
38: import java.nio.file.Paths;
39: import it.tidalwave.util.annotation.VisibleForTesting;
40: import lombok.experimental.UtilityClass;
41: import lombok.extern.slf4j.Slf4j;
42: import static java.text.Normalizer.Form.*;
43:
44: /***********************************************************************************************************************
45: *
46: * See the related test for detailed information.
47: *
48: * @author Fabrizio Giudici
49: *
50: **********************************************************************************************************************/
51: @UtilityClass @Slf4j
52: public class PathNormalization
53: {
54: private static final Normalizer.Form NATIVE_FORM;
55:
56: static
57: {
58: final String osName = System.getProperty("os.name").toLowerCase();
59:
60:• switch (osName)
61: {
62: case "linux":
63: NATIVE_FORM = NFC;
64: break;
65:
66: case "mac os x":
67: NATIVE_FORM = NFD;
68: break;
69:
70: case "windows":
71: NATIVE_FORM = NFD; // FIXME: just guessing
72: break;
73:
74: default:
75: throw new ExceptionInInitializerError("Unknown o.s.: " + osName);
76: }
77:
78: log.info("Charset normalizer form: {}", NATIVE_FORM);
79: }
80:
81: /*******************************************************************************************************************
82: *
83: * Takes a path that maps to an existing file, and in case it can't be resolved, it tries to replace with an
84: * equivalent representation of an existing path, with the native form of character encoding (i.e. the one used
85: * by the file system).
86: * If there is no normalized path to replace with, the original path is returned.
87: * Note that this method is I/O heavy, as it must access the file system.
88: * FIXME: what about using a cache?
89: *
90: * See http://askubuntu.com/questions/533690/rsync-with-special-character-files-not-working-between-mac-and-linux
91: *
92: * @param path the path
93: * @return the normalized path
94: *
95: ******************************************************************************************************************/
96: @Nonnull
97: public static Path fixedPath (@Nonnull final Path path)
98: throws IOException
99: {
100: // log.trace("fixedPath({})", path);
101:
102:• if (Files.exists(path)) // can be normally found, no need to process it
103: {
104: return path;
105: }
106:
107: Path pathSoFar = Paths.get("/");
108:
109:• for (final Path segment : path.toAbsolutePath())
110: {
111: // log.trace(">>>> pathSoFar: {} segment: {}", pathSoFar, segment);
112: final Path resolved = pathSoFar.resolve(segment);
113:
114:• if (Files.exists(resolved))
115: {
116: pathSoFar = resolved;
117: }
118: else // didn't find 'resolved' because of wrong normalisation, searching in alternative way
119: {
120: try (final Stream<Path> stream = Files.list(pathSoFar))
121: {
122: final Optional<Path> child = stream.map(Path::getFileName)
123: .filter(p -> equalsNormalized(segment, p))
124: .findFirst();
125:• if (child.isEmpty())
126: {
127: log.warn(">>>> fixing failed at: {}", pathSoFar);
128: return path;
129: }
130:
131: pathSoFar = pathSoFar.resolve(child.get());
132:• assert Files.exists(pathSoFar) : "Fixing failed at: " + pathSoFar;
133:• }
134: }
135: }
136:
137: return pathSoFar;
138: }
139:
140: /*******************************************************************************************************************
141: *
142: * Checks whether two Paths are equal after normalisation of their string representation.
143: *
144: * @param path1 the former path
145: * @param path2 the latter path
146: * @return {@code true} if they are equal
147: *
148: ******************************************************************************************************************/
149: @VisibleForTesting static boolean equalsNormalized (@Nonnull final Path path1, @Nonnull final Path path2)
150: {
151: return Objects.equals(normalizedToNativeForm(path1.toString()), normalizedToNativeForm(path2.toString()));
152: }
153:
154: /*******************************************************************************************************************
155: *
156: *
157: *
158: ******************************************************************************************************************/
159: @Nullable
160: public static String normalizedToNativeForm (@Nullable final String string)
161: {
162:• return (string == null) ? null : Normalizer.normalize(string, NATIVE_FORM);
163: }
164: }