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 }