comparison hg4j/src/main/java/org/tmatesoft/hg/internal/StoragePathHelper.java @ 213:6ec4af642ba8 gradle

Project uses Gradle for build - actual changes
author Alexander Kitaev <kitaev@gmail.com>
date Tue, 10 May 2011 10:52:53 +0200
parents
children
comparison
equal deleted inserted replaced
212:edb2e2829352 213:6ec4af642ba8
1 /*
2 * Copyright (c) 2011 TMate Software Ltd
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 2 of the License.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * For information on how to redistribute this software under
14 * the terms of a license other than GNU General Public License
15 * contact TMate Software at support@hg4j.com
16 */
17 package org.tmatesoft.hg.internal;
18
19 import java.util.Arrays;
20 import java.util.TreeSet;
21
22 import org.tmatesoft.hg.util.PathRewrite;
23
24 /**
25 * @see http://mercurial.selenic.com/wiki/CaseFoldingPlan
26 * @see http://mercurial.selenic.com/wiki/fncacheRepoFormat
27 *
28 * @author Artem Tikhomirov
29 * @author TMate Software Ltd.
30 */
31 class StoragePathHelper implements PathRewrite {
32
33 private final boolean store;
34 private final boolean fncache;
35 private final boolean dotencode;
36
37 public StoragePathHelper(boolean isStore, boolean isFncache, boolean isDotencode) {
38 store = isStore;
39 fncache = isFncache;
40 dotencode = isDotencode;
41 }
42
43 // FIXME document what path argument is, whether it includes .i or .d, and whether it's 'normalized' (slashes) or not.
44 // since .hg/store keeps both .i files and files without extension (e.g. fncache), guees, for data == false
45 // we shall assume path has extension
46 public String rewrite(String path) {
47 final String STR_STORE = "store/";
48 final String STR_DATA = "data/";
49 final String STR_DH = "dh/";
50 final String reservedChars = "\\:*?\"<>|";
51 char[] hexByte = new char[2];
52
53 path = path.replace(".hg/", ".hg.hg/").replace(".i/", ".i.hg/").replace(".d/", ".d.hg/");
54 StringBuilder sb = new StringBuilder(path.length() << 1);
55 if (store || fncache) {
56 // encodefilename
57 for (int i = 0; i < path.length(); i++) {
58 final char ch = path.charAt(i);
59 if (ch >= 'a' && ch <= 'z') {
60 sb.append(ch); // POIRAE
61 } else if (ch >= 'A' && ch <= 'Z') {
62 sb.append('_');
63 sb.append(Character.toLowerCase(ch)); // Perhaps, (char) (((int) ch) + 32)? Even better, |= 0x20?
64 } else if (reservedChars.indexOf(ch) != -1) {
65 sb.append('~');
66 sb.append(toHexByte(ch, hexByte));
67 } else if ((ch >= '~' /*126*/ && ch <= 255) || ch < ' ' /*32*/) {
68 sb.append('~');
69 sb.append(toHexByte(ch, hexByte));
70 } else if (ch == '_') {
71 sb.append('_');
72 sb.append('_');
73 } else {
74 sb.append(ch);
75 }
76 }
77 // auxencode
78 if (fncache) {
79 encodeWindowsDeviceNames(sb);
80 }
81 }
82 final int MAX_PATH_LEN = 120;
83 if (fncache && (sb.length() + STR_DATA.length() + ".i".length() > MAX_PATH_LEN)) {
84 String digest = new DigestHelper().sha1(STR_DATA, path, ".i").asHexString();
85 final int DIR_PREFIX_LEN = 8;
86 // not sure why (-4) is here. 120 - 40 = up to 80 for path with ext. dh/ + ext(.i) = 3+2
87 final int MAX_DIR_PREFIX = 8 * (DIR_PREFIX_LEN + 1) - 4;
88 sb = new StringBuilder(MAX_PATH_LEN);
89 for (int i = 0; i < path.length(); i++) {
90 final char ch = path.charAt(i);
91 if (ch >= 'a' && ch <= 'z') {
92 sb.append(ch);
93 } else if (ch >= 'A' && ch <= 'Z') {
94 sb.append((char) (ch | 0x20)); // lowercase
95 } else if (reservedChars.indexOf(ch) != -1) {
96 sb.append('~');
97 sb.append(toHexByte(ch, hexByte));
98 } else if ((ch >= '~' /*126*/ && ch <= 255) || ch < ' ' /*32*/) {
99 sb.append('~');
100 sb.append(toHexByte(ch, hexByte));
101 } else {
102 sb.append(ch);
103 }
104 }
105 encodeWindowsDeviceNames(sb);
106 int fnameStart = sb.lastIndexOf("/"); // since we rewrite file names, it never ends with slash (for dirs, I'd pass length-2);
107 StringBuilder completeHashName = new StringBuilder(MAX_PATH_LEN);
108 completeHashName.append(STR_STORE);
109 completeHashName.append(STR_DH);
110 if (fnameStart == -1) {
111 // no dirs, just long filename
112 sb.setLength(MAX_PATH_LEN - 40 /*digest.length()*/ - STR_DH.length() - ".i".length());
113 completeHashName.append(sb);
114 } else {
115 StringBuilder sb2 = new StringBuilder(MAX_PATH_LEN);
116 int x = 0;
117 do {
118 int i = sb.indexOf("/", x);
119 final int sb2Len = sb2.length();
120 if (i-x <= DIR_PREFIX_LEN) { // a b c d e f g h /
121 sb2.append(sb, x, i + 1); // with slash
122 } else {
123 sb2.append(sb, x, x + DIR_PREFIX_LEN);
124 // may unexpectedly end with bad character
125 final int last = sb2.length()-1;
126 char lastChar = sb2.charAt(last);
127 assert lastChar == sb.charAt(x + DIR_PREFIX_LEN - 1);
128 if (lastChar == '.' || lastChar == ' ') {
129 sb2.setCharAt(last, '_');
130 }
131 sb2.append('/');
132 }
133 if (sb2.length()-1 > MAX_DIR_PREFIX) {
134 sb2.setLength(sb2Len); // strip off last segment, it's too much
135 break;
136 }
137 x = i+1;
138 } while (x < fnameStart);
139 assert sb2.charAt(sb2.length() - 1) == '/';
140 int left = MAX_PATH_LEN - sb2.length() - 40 /*digest.length()*/ - STR_DH.length() - ".i".length();
141 assert left >= 0;
142 fnameStart++; // move from / to actual name
143 sb2.append(sb, fnameStart, fnameStart + left > sb.length() ? sb.length() : fnameStart+left);
144 completeHashName.append(sb2);
145 }
146 completeHashName.append(digest);
147 sb = completeHashName;
148 } else if (store) {
149 sb.insert(0, STR_STORE + STR_DATA);
150 }
151 sb.append(".i");
152 return sb.toString();
153 }
154
155 private void encodeWindowsDeviceNames(StringBuilder sb) {
156 char[] hexByte = new char[2];
157 int x = 0; // last segment start
158 final TreeSet<String> windowsReservedFilenames = new TreeSet<String>();
159 windowsReservedFilenames.addAll(Arrays.asList("con prn aux nul com1 com2 com3 com4 com5 com6 com7 com8 com9 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9".split(" ")));
160 do {
161 int i = sb.indexOf("/", x);
162 if (i == -1) {
163 i = sb.length();
164 }
165 // windows reserved filenames are at least of length 3
166 if (i - x >= 3) {
167 boolean found = false;
168 if (i-x == 3 || i-x == 4) {
169 found = windowsReservedFilenames.contains(sb.subSequence(x, i));
170 } else if (sb.charAt(x+3) == '.') { // implicit i-x > 3
171 found = windowsReservedFilenames.contains(sb.subSequence(x, x+3));
172 } else if (i-x > 4 && sb.charAt(x+4) == '.') {
173 found = windowsReservedFilenames.contains(sb.subSequence(x, x+4));
174 }
175 if (found) {
176 sb.insert(x+3, toHexByte(sb.charAt(x+2), hexByte));
177 sb.setCharAt(x+2, '~');
178 i += 2;
179 }
180 }
181 if (dotencode && (sb.charAt(x) == '.' || sb.charAt(x) == ' ')) {
182 sb.insert(x+1, toHexByte(sb.charAt(x), hexByte));
183 sb.setCharAt(x, '~'); // setChar *after* charAt/insert to get ~2e, not ~7e for '.'
184 i += 2;
185 }
186 x = i+1;
187 } while (x < sb.length());
188 }
189
190 private static char[] toHexByte(int ch, char[] buf) {
191 assert buf.length > 1;
192 final String hexDigits = "0123456789abcdef";
193 buf[0] = hexDigits.charAt((ch & 0x00F0) >>> 4);
194 buf[1] = hexDigits.charAt(ch & 0x0F);
195 return buf;
196 }
197 }