Mercurial > hg4j
comparison hg4j/src/main/java/org/tmatesoft/hg/internal/NewlineFilter.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 static org.tmatesoft.hg.internal.Filter.Direction.FromRepo; | |
20 import static org.tmatesoft.hg.internal.Filter.Direction.ToRepo; | |
21 import static org.tmatesoft.hg.internal.KeywordFilter.copySlice; | |
22 | |
23 import java.io.File; | |
24 import java.io.FileInputStream; | |
25 import java.io.FileOutputStream; | |
26 import java.nio.ByteBuffer; | |
27 import java.util.ArrayList; | |
28 import java.util.Map; | |
29 | |
30 import org.tmatesoft.hg.repo.HgInternals; | |
31 import org.tmatesoft.hg.repo.HgRepository; | |
32 import org.tmatesoft.hg.util.Path; | |
33 | |
34 /** | |
35 * | |
36 * @author Artem Tikhomirov | |
37 * @author TMate Software Ltd. | |
38 */ | |
39 public class NewlineFilter implements Filter { | |
40 | |
41 // if allowInconsistent is true, filter simply pass incorrect newline characters (single \r or \r\n on *nix and single \n on Windows) as is, | |
42 // i.e. doesn't try to convert them into appropriate newline characters. XXX revisit if Keyword extension behaves differently | |
43 private final boolean allowInconsistent; | |
44 private final boolean winToNix; | |
45 | |
46 private NewlineFilter(boolean failIfInconsistent, int transform) { | |
47 winToNix = transform == 0; | |
48 allowInconsistent = !failIfInconsistent; | |
49 } | |
50 | |
51 public ByteBuffer filter(ByteBuffer src) { | |
52 if (winToNix) { | |
53 return win2nix(src); | |
54 } else { | |
55 return nix2win(src); | |
56 } | |
57 } | |
58 | |
59 private ByteBuffer win2nix(ByteBuffer src) { | |
60 int x = src.position(); // source index | |
61 int lookupStart = x; | |
62 ByteBuffer dst = null; | |
63 while (x < src.limit()) { | |
64 // x, lookupStart, ir and in are absolute positions within src buffer, which is never read with modifying operations | |
65 int ir = indexOf('\r', src, lookupStart); | |
66 int in = indexOf('\n', src, lookupStart); | |
67 if (ir == -1) { | |
68 if (in == -1 || allowInconsistent) { | |
69 if (dst != null) { | |
70 copySlice(src, x, src.limit(), dst); | |
71 x = src.limit(); // consumed all | |
72 } | |
73 break; | |
74 } else { | |
75 fail(src, in); | |
76 } | |
77 } | |
78 // in == -1 while ir != -1 may be valid case if ir is the last char of the buffer, we check below for that | |
79 if (in != -1 && in != ir+1 && !allowInconsistent) { | |
80 fail(src, in); | |
81 } | |
82 if (dst == null) { | |
83 dst = ByteBuffer.allocate(src.remaining()); | |
84 } | |
85 copySlice(src, x, ir, dst); | |
86 if (ir+1 == src.limit()) { | |
87 // last char of the buffer - | |
88 // consume src till that char and let next iteration work on it | |
89 x = ir; | |
90 break; | |
91 } | |
92 if (in != ir + 1) { | |
93 x = ir+1; // generally in, but if allowInconsistent==true and \r is not followed by \n, then | |
94 // cases like "one \r two \r\n three" shall be processed correctly (second pair would be ignored if x==in) | |
95 lookupStart = ir+1; | |
96 } else { | |
97 x = in; | |
98 lookupStart = x+1; // skip \n for next lookup | |
99 } | |
100 } | |
101 src.position(x); // mark we've consumed up to x | |
102 return dst == null ? src : (ByteBuffer) dst.flip(); | |
103 } | |
104 | |
105 private ByteBuffer nix2win(ByteBuffer src) { | |
106 int x = src.position(); | |
107 ByteBuffer dst = null; | |
108 while (x < src.limit()) { | |
109 int in = indexOf('\n', src, x); | |
110 int ir = indexOf('\r', src, x, in == -1 ? src.limit() : in); | |
111 if (in == -1) { | |
112 if (ir == -1 || allowInconsistent) { | |
113 break; | |
114 } else { | |
115 fail(src, ir); | |
116 } | |
117 } else if (ir != -1 && !allowInconsistent) { | |
118 fail(src, ir); | |
119 } | |
120 | |
121 // x <= in < src.limit | |
122 // allowInconsistent && x <= ir < in || ir == -1 | |
123 if (dst == null) { | |
124 // buffer full of \n grows as much as twice in size | |
125 dst = ByteBuffer.allocate(src.remaining() * 2); | |
126 } | |
127 copySlice(src, x, in, dst); | |
128 if (ir == -1 || ir+1 != in) { | |
129 dst.put((byte) '\r'); | |
130 } // otherwise (ir!=-1 && ir+1==in) we found \r\n pair, don't convert to \r\r\n | |
131 // we may copy \n at src[in] on the next iteration, but would need extra lookupIndex variable then. | |
132 dst.put((byte) '\n'); | |
133 x = in+1; | |
134 } | |
135 src.position(x); | |
136 return dst == null ? src : (ByteBuffer) dst.flip(); | |
137 } | |
138 | |
139 | |
140 private void fail(ByteBuffer b, int pos) { | |
141 throw new RuntimeException(String.format("Inconsistent newline characters in the stream (char 0x%x, local index:%d)", b.get(pos), pos)); | |
142 } | |
143 | |
144 private static int indexOf(char ch, ByteBuffer b, int from) { | |
145 return indexOf(ch, b, from, b.limit()); | |
146 } | |
147 | |
148 // looks up in buf[from..to) | |
149 private static int indexOf(char ch, ByteBuffer b, int from, int to) { | |
150 for (int i = from; i < to; i++) { | |
151 byte c = b.get(i); | |
152 if (ch == c) { | |
153 return i; | |
154 } | |
155 } | |
156 return -1; | |
157 } | |
158 | |
159 public static class Factory implements Filter.Factory { | |
160 private boolean failIfInconsistent = true; | |
161 private Path.Matcher lfMatcher; | |
162 private Path.Matcher crlfMatcher; | |
163 private Path.Matcher binMatcher; | |
164 private Path.Matcher nativeMatcher; | |
165 private String nativeRepoFormat; | |
166 private String nativeOSFormat; | |
167 | |
168 public void initialize(HgRepository hgRepo, ConfigFile cfg) { | |
169 failIfInconsistent = cfg.getBoolean("eol", "only-consistent", true); | |
170 File cfgFile = new File(new HgInternals(hgRepo).getRepositoryDir().getParentFile(), ".hgeol"); | |
171 if (!cfgFile.canRead()) { | |
172 return; | |
173 } | |
174 // XXX if .hgeol is not checked out, we may get it from repository | |
175 // HgDataFile cfgFileNode = hgRepo.getFileNode(".hgeol"); | |
176 // if (!cfgFileNode.exists()) { | |
177 // return; | |
178 // } | |
179 // XXX perhaps, add HgDataFile.hasWorkingCopy and workingCopyContent()? | |
180 ConfigFile hgeol = new ConfigFile(); | |
181 hgeol.addLocation(cfgFile); | |
182 nativeRepoFormat = hgeol.getSection("repository").get("native"); | |
183 if (nativeRepoFormat == null) { | |
184 nativeRepoFormat = "LF"; | |
185 } | |
186 final String os = System.getProperty("os.name"); // XXX need centralized set of properties | |
187 nativeOSFormat = os.indexOf("Windows") != -1 ? "CRLF" : "LF"; | |
188 // I assume pattern ordering in .hgeol is not important | |
189 ArrayList<String> lfPatterns = new ArrayList<String>(); | |
190 ArrayList<String> crlfPatterns = new ArrayList<String>(); | |
191 ArrayList<String> nativePatterns = new ArrayList<String>(); | |
192 ArrayList<String> binPatterns = new ArrayList<String>(); | |
193 for (Map.Entry<String,String> e : hgeol.getSection("patterns").entrySet()) { | |
194 if ("CRLF".equals(e.getValue())) { | |
195 crlfPatterns.add(e.getKey()); | |
196 } else if ("LF".equals(e.getValue())) { | |
197 lfPatterns.add(e.getKey()); | |
198 } else if ("native".equals(e.getValue())) { | |
199 nativePatterns.add(e.getKey()); | |
200 } else if ("BIN".equals(e.getValue())) { | |
201 binPatterns.add(e.getKey()); | |
202 } else { | |
203 System.out.printf("Can't recognize .hgeol entry: %s for %s", e.getValue(), e.getKey()); // FIXME log warning | |
204 } | |
205 } | |
206 if (!crlfPatterns.isEmpty()) { | |
207 crlfMatcher = new PathGlobMatcher(crlfPatterns.toArray(new String[crlfPatterns.size()])); | |
208 } | |
209 if (!lfPatterns.isEmpty()) { | |
210 lfMatcher = new PathGlobMatcher(lfPatterns.toArray(new String[lfPatterns.size()])); | |
211 } | |
212 if (!binPatterns.isEmpty()) { | |
213 binMatcher = new PathGlobMatcher(binPatterns.toArray(new String[binPatterns.size()])); | |
214 } | |
215 if (!nativePatterns.isEmpty()) { | |
216 nativeMatcher = new PathGlobMatcher(nativePatterns.toArray(new String[nativePatterns.size()])); | |
217 } | |
218 } | |
219 | |
220 public Filter create(Path path, Options opts) { | |
221 if (binMatcher == null && crlfMatcher == null && lfMatcher == null && nativeMatcher == null) { | |
222 // not initialized - perhaps, no .hgeol found | |
223 return null; | |
224 } | |
225 if (binMatcher != null && binMatcher.accept(path)) { | |
226 return null; | |
227 } | |
228 if (crlfMatcher != null && crlfMatcher.accept(path)) { | |
229 return new NewlineFilter(failIfInconsistent, 1); | |
230 } else if (lfMatcher != null && lfMatcher.accept(path)) { | |
231 return new NewlineFilter(failIfInconsistent, 0); | |
232 } else if (nativeMatcher != null && nativeMatcher.accept(path)) { | |
233 if (nativeOSFormat.equals(nativeRepoFormat)) { | |
234 return null; | |
235 } | |
236 if (opts.getDirection() == FromRepo) { | |
237 int transform = "CRLF".equals(nativeOSFormat) ? 1 : 0; | |
238 return new NewlineFilter(failIfInconsistent, transform); | |
239 } else if (opts.getDirection() == ToRepo) { | |
240 int transform = "CRLF".equals(nativeOSFormat) ? 0 : 1; | |
241 return new NewlineFilter(failIfInconsistent, transform); | |
242 } | |
243 return null; | |
244 } | |
245 return null; | |
246 } | |
247 } | |
248 | |
249 public static void main(String[] args) throws Exception { | |
250 FileInputStream fis = new FileInputStream(new File("/temp/design.lf.txt")); | |
251 FileOutputStream fos = new FileOutputStream(new File("/temp/design.newline.out")); | |
252 ByteBuffer b = ByteBuffer.allocate(12); | |
253 NewlineFilter nlFilter = new NewlineFilter(true, 1); | |
254 while (fis.getChannel().read(b) != -1) { | |
255 b.flip(); // get ready to be read | |
256 ByteBuffer f = nlFilter.filter(b); | |
257 fos.getChannel().write(f); // XXX in fact, f may not be fully consumed | |
258 if (b.hasRemaining()) { | |
259 b.compact(); | |
260 } else { | |
261 b.clear(); | |
262 } | |
263 } | |
264 fis.close(); | |
265 fos.flush(); | |
266 fos.close(); | |
267 } | |
268 | |
269 } |