77import java .io .Writer ;
88import java .nio .charset .StandardCharsets ;
99import java .nio .file .Paths ;
10+ import java .util .Arrays ;
1011import java .util .Collection ;
1112import java .util .TreeSet ;
1213import org .apache .commons .io .FileUtils ;
@@ -60,6 +61,24 @@ public void mkOutputFile(String name, WriteAction filler) throws IOException {
6061 mkOutputFile (name , false , filler );
6162 }
6263
64+ /**
65+ * Creates a UTF-8 encoded file with the given name. If name is a path, all
66+ * missing directories will be created as well. The given name will be
67+ * joined with the workdir used to initialize this file manager, thus
68+ * raising an exception if {@code name} is absolute. {@code filler} will
69+ * then be used to write to the writer.
70+ *
71+ * @param path the sub-path, relative to the workdir, where the file
72+ * should be created
73+ * @param name the name of the file to create
74+ * @param filler the callback to write to the file
75+ *
76+ * @throws IOException if something goes wrong while creating the file
77+ */
78+ public void mkOutputFile (String path , String name , WriteAction filler ) throws IOException {
79+ mkOutputFile (path , name , false , filler );
80+ }
81+
6382 /**
6483 * Creates a UTF-8 encoded file with the given name, appending the
6584 * {@code dot} extension. If name is a path, all missing directories will be
@@ -78,6 +97,26 @@ public void mkDotFile(String name, WriteAction filler) throws IOException {
7897 mkOutputFile (cleanupForDotFile (name ) + ".dot" , false , filler );
7998 }
8099
100+ /**
101+ * Creates a UTF-8 encoded file with the given name, appending the
102+ * {@code dot} extension. If name is a path, all missing directories will be
103+ * created as well. The name will be stripped of any characters that might
104+ * cause problems in the file name. The given name will be joined with the
105+ * workdir used to initialize this file manager, thus raising an exception
106+ * if {@code name} is absolute. {@code filler} will then be used to write to
107+ * the writer.
108+ *
109+ * @param path the sub-path, relative to the workdir, where the file
110+ * should be created
111+ * @param name the name of the file to create
112+ * @param filler the callback to write to the file
113+ *
114+ * @throws IOException if something goes wrong while creating the file
115+ */
116+ public void mkDotFile (String path , String name , WriteAction filler ) throws IOException {
117+ mkOutputFile (path , cleanupForDotFile (name ) + ".dot" , false , filler );
118+ }
119+
81120 /**
82121 * A functional interface for a write operation that can throw
83122 * {@link IOException}s.
@@ -112,20 +151,63 @@ public interface WriteAction {
112151 * the file
113152 */
114153 public void mkOutputFile (String name , boolean bom , WriteAction filler ) throws IOException {
115- File file = new File (workdir , name );
154+ mkOutputFile (null , name , bom , filler );
155+ }
156+
157+ /**
158+ * Creates a UTF-8 encoded file with the given name. If name is a path, all
159+ * missing directories will be created as well. The given name will be
160+ * joined with the workdir used to initialize this file manager, thus
161+ * raising an exception if {@code name} is absolute. {@code filler} will
162+ * then be used to write to the writer.
163+ *
164+ * @param path the sub-path, relative to the workdir, where the file
165+ * should be created
166+ * @param name the name of the file to create
167+ * @param bom if {@code true}, the bom marker {@code \ufeff} will be
168+ * written to the file
169+ * @param filler the callback to write to the file
170+ *
171+ * @throws IOException if something goes wrong while creating or writing to
172+ * the file
173+ */
174+ public void mkOutputFile (String path , String name , boolean bom , WriteAction filler ) throws IOException {
175+ File parent = workdir ;
176+ if (path != null )
177+ parent = new File (workdir , cleanFileName (path , true ));
178+ File file = new File (parent , cleanFileName (name , false ));
116179
117- File parent = file .getAbsoluteFile ().getParentFile ();
118180 if (!parent .exists () && !parent .mkdirs ())
119181 throw new IOException ("Unable to create directory structure for " + file );
120182
121- createdFiles .add (name );
183+ createdFiles .add (workdir . toPath (). relativize ( file . toPath ()). toString () );
122184 try (Writer writer = new OutputStreamWriter (new FileOutputStream (file ), StandardCharsets .UTF_8 .newEncoder ())) {
123185 if (bom )
124186 writer .write ('\ufeff' );
125187 filler .perform (writer );
126188 }
127189 }
128190
191+ private final static int [] illegalChars = { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 , 16 , 17 , 18 , 19 ,
192+ 20 , 21 , 22 , 23 , 24 , 25 , 26 , 27 , 28 , 29 , 30 , 31 , 34 , 42 , 47 , 58 , 60 , 62 , 63 , 92 , 124 };
193+
194+ private static String cleanFileName (String name , boolean keepDirSeparator ) {
195+ // https://stackoverflow.com/questions/1155107/is-there-a-cross-platform-java-method-to-remove-filename-special-chars
196+ StringBuilder cleanName = new StringBuilder ();
197+ int len = name .codePointCount (0 , name .length ());
198+ for (int i = 0 ; i < len ; i ++) {
199+ int c = name .codePointAt (i );
200+ // 57 is / and 92 is \
201+ if ((keepDirSeparator && (c == 57 || c == 92 ))
202+ || Arrays .binarySearch (illegalChars , c ) < 0 )
203+ cleanName .appendCodePoint (c );
204+ else
205+ cleanName .appendCodePoint ('_' );
206+
207+ }
208+ return cleanName .toString ();
209+ }
210+
129211 private static String cleanupForDotFile (String name ) {
130212 String result = name .replace (' ' , '_' );
131213 result = result .replace ("::" , "." );
0 commit comments