1 /**
2 * From Apache Uima migration utils file at
3 * http://svn.apache.org/repos/asf/incubator/uima/uimaj/trunk/uimaj-tools/src/main/java/org/apache/uima
4 * /tools/migration/IbmUimaToApacheUima.java
5 */
6 package org.andromda.utils;
7
8 import java.io.File;
9 import java.io.FileInputStream;
10 import java.io.FileOutputStream;
11 import java.io.IOException;
12 import java.util.ArrayList;
13 import java.util.Collection;
14 import java.util.HashSet;
15 import java.util.List;
16 import java.util.Set;
17 import java.util.regex.Matcher;
18 import java.util.regex.Pattern;
19 import java.util.zip.ZipEntry;
20 import java.util.zip.ZipInputStream;
21 import java.util.zip.ZipOutputStream;
22 import org.apache.commons.io.FileUtils;
23 import org.apache.commons.io.filefilter.FileFilterUtils;
24 import org.apache.commons.io.filefilter.IOFileFilter;
25 import org.apache.commons.lang.StringUtils;
26
27 /**
28 * Iterate down through a directory hierarchy, replace @andromda.whatever.whatever with the Java compliant andromda_whatever_whatever tagged
29 * value (attribute) format. Open .zip files if necessary.
30 *
31 * @author RJF3
32 */
33 public class GlobalPatternFileReplace
34 {
35 private static List<Replacement> replacements = new ArrayList<Replacement>();
36 private static int MAX_FILE_SIZE = 10000000; // don't update files bigger than 10 MB
37 private static Set<String> extensions = new HashSet<String>();
38 private static int filesScanned = 0;
39 private static int filesModified = 0;
40 private static int fileReplacements = 0;
41
42 /**
43 *
44 */
45 public GlobalPatternFileReplace()
46 {
47 // TODO Auto-generated constructor stub
48 }
49
50 /**
51 * @param args
52 * @throws IOException
53 */
54 public static void main(String[] args) throws IOException
55 {
56 File file = new File("C:/workspaces/A34/andromda341/cartridges/andromda-spring/pom.xml");
57 // parse command line
58 String dir = null;
59 for (int i = 0; i < args.length; i++)
60 {
61 if (args[i].startsWith("-"))
62 {
63 if (args[i].equals("-ext"))
64 {
65 if (i + 1 >= args.length)
66 {
67 printUsageAndExit();
68 }
69 parseCommaSeparatedList(args[++i], extensions);
70 }
71 else
72 {
73 System.err.println("Unknown switch " + args[i]);
74 printUsageAndExit();
75 }
76 }
77 else
78 {
79 if (dir != null)
80 {
81 printUsageAndExit();
82 }
83 else
84 {
85 dir = args[i];
86 file = new File(dir);
87 }
88 }
89 }
90 /*if (dir == null)
91 {
92 //file = new File(".");
93 //System.out.println("Using default current directory: " + file.getCanonicalPath());
94 file = new File("C:/workspaces/A34/andromda341/cartridges/andromda-spring");
95 //printUsageAndExit();
96 }
97 if (file == null)
98 {
99 printUsageAndExit();
100 return;
101 }*/
102 if (extensions.isEmpty())
103 {
104 parseCommaSeparatedList("xml,java,xml.zip,vsl,vm", extensions);
105 }
106
107 try
108 {
109 /*
110 replaceInFile(file); //read resource files //map from IBM UIMA package names to Apache UIMA package names
111 */
112 readMapping("packageMapping.txt", replacements, true);
113 //other string replacements readMapping("stringReplacements.txt", replacements, false);
114
115 // from system property, get list of file extensions to exclude
116 // do the replacements
117 System.out.println("Migrating your files in " + file.getCanonicalPath());
118 replaceInAllFiles(file);
119 }
120 catch (IOException e)
121 {
122 // TODO Auto-generated catch block
123 e.printStackTrace();
124 }
125
126 System.out.println("Migration complete.");
127 System.out.println("Scanned " + filesScanned + " files. " + filesModified + " files modified.");
128 }
129
130 /**
131 *
132 */
133 private static void printUsageAndExit()
134 {
135 System.err.println("Usage: java " + GlobalPatternFileReplace.class.getName() + " <directory> [-ext <fileExtensions>]");
136 System.err.println("<fileExtensions> is a comma separated list of file extensions to process, e.g.: java,xml,properties");
137 System.err.println("\tUse a trailing comma to include files with no extension (meaning their name contains no dot)");
138 System.exit(1);
139 }
140
141 /**
142 * Applies the necessary replacements to all files in the given directory. Subdirectories are processed recursively.
143 * Filters out SVN files and target directory by default.
144 *
145 * @param dir directory containing files to replace
146 * @throws IOException if an I/O error occurs
147 */
148 private static void replaceInAllFiles(File dir) throws IOException
149 {
150 if (dir.isDirectory())
151 {
152 IOFileFilter fileFilter = FileFilterUtils.trueFileFilter();
153 IOFileFilter notTargetFileFilter = FileFilterUtils.notFileFilter(FileFilterUtils.nameFileFilter("target"));
154 IOFileFilter dirFilter = FileFilterUtils.and(FileFilterUtils.makeSVNAware(null), notTargetFileFilter);
155 Collection<File> files = FileUtils.listFiles(dir, fileFilter, dirFilter);
156 for (File file : files)
157 {
158 if (file.isFile())
159 {
160 boolean process = false;
161 // Check files against extensions filter
162 for (String ext : extensions)
163 {
164 if (file.getName().endsWith("." + ext))
165 {
166 process = true;
167 break;
168 }
169 }
170 if (process)
171 {
172 // do the replacements
173 // for .zip files, unzip, replace, and put back in archive
174 if (file.getName().endsWith(".zip"))
175 {
176 replaceInArchive(file);
177 }
178 else
179 {
180 replaceInFile(file);
181 }
182 }
183 }
184
185 // recursively call on subdirectories
186 else if (file.isDirectory())
187 {
188 replaceInAllFiles(file);
189 }
190 }
191 }
192 if (dir.isFile())
193 {
194 if (dir.getName().endsWith(".zip"))
195 {
196 replaceInArchive(dir);
197 }
198 else
199 {
200 replaceInFile(dir);
201 }
202 }
203 }
204
205 /**
206 * Checks if file exists and is not excluded by extension and is writable.
207 *
208 * @param file the file to process
209 */
210 private static boolean checkAttributes(File file) throws IOException
211 {
212 // skip files with extensions specified in the excludes list
213 if (!extensions.isEmpty())
214 {
215 String filename = file.getName();
216 String ext = "";
217 int lastDot = filename.lastIndexOf('.');
218 if (lastDot > -1)
219 {
220 ext = filename.substring(lastDot + 1);
221 }
222 if (!extensions.contains(ext.toLowerCase()))
223 {
224 return false;
225 }
226 }
227
228 // make writable files that are readonly
229 if (!file.canRead())
230 {
231 //new FilePermission(file.getCanonicalPath(), "read,write,execute,delete");
232 //file.setReadable(true, false);
233 System.err.println("Can't read file: " + file.getCanonicalPath());
234 //continue;
235 }
236 /*if (!file.canWrite())
237 {
238 System.err.println("Skipping unwritable file: " + file.getCanonicalPath());
239 return false;
240 }*/
241 // skip files that are too big
242 if (file.length() > MAX_FILE_SIZE)
243 {
244 System.out.println("Skipping file " + file.getCanonicalPath() + " with size: " + file.length() + " bytes");
245 return false;
246 }
247
248 return true;
249 }
250
251 /**
252 * Applies replacements to a single file.
253 *
254 * @param file the file to process
255 */
256 private static int replaceInArchive(File file) throws IOException
257 {
258 // do the replacements in each file in the archive
259
260 int replacements = 0;
261 // for .zip files, unzip, replace, and put back in archive
262 if (file.isFile() && file.getName().endsWith(".zip"))
263 {
264 File location = unpack(file);
265 IOFileFilter fileFilter = FileFilterUtils.suffixFileFilter("xml");
266 IOFileFilter dirFilter = FileFilterUtils.makeCVSAware(null);
267 Collection<File> archiveFiles = FileUtils.listFiles(location, fileFilter, dirFilter);
268 for (File archiveFile : archiveFiles)
269 {
270 // Only update zip archive contents if they have changed
271 int replacement = replaceInFile(archiveFile);
272 if (replacement > 0)
273 {
274 pack(file, archiveFile.getAbsolutePath());
275 replacements += replacement;
276 }
277 archiveFile.delete();
278 }
279 location.delete();
280 }
281 return replacements;
282 }
283
284 /**
285 * Applies replacements to a single file.
286 *
287 * @param file the file to process
288 */
289 private static int replaceInFile(File file) throws IOException
290 {
291 // do the replacements
292 fileReplacements = 0;
293 if (!checkAttributes(file))
294 {
295 return fileReplacements;
296 }
297 String original;
298 try
299 {
300 original = FileUtils.readFileToString(file);
301 }
302 catch (IOException e)
303 {
304 System.err.println("Error reading " + file.getCanonicalPath());
305 System.err.println(e.getMessage());
306 return fileReplacements;
307 }
308 String contents = original;
309 contents = replaceInPattern(contents);
310
311 // write file if it changed
312 if (!contents.equals(original))
313 {
314 if (!file.canWrite())
315 {
316 // JDK 1.6 only. How do we make file writable in JDK 1.5?
317 file.setReadable(true, false);
318 System.out.println("Making file writable: " + file.getCanonicalPath());
319 //new FilePermission(file.getCanonicalPath(), "read,write,execute,delete");
320 }
321 if (!file.canWrite())
322 {
323 System.err.println("Could not make file writable: " + file.getCanonicalPath());
324 }
325 else
326 {
327 FileUtils.writeStringToFile(file, contents);
328 filesModified++;
329 }
330 System.out.println(file.getAbsolutePath() + " " + fileReplacements + " replacements");
331 }
332 filesScanned++;
333
334 // check for situations that may need manual attention,
335 // updates filesNeedingManualAttention field
336 // checkForManualAttentionNeeded(file, original);
337 return fileReplacements;
338 }
339
340 /**
341 * Unpacks the archive file.
342 *
343 * @param file File to be unpacked.
344 * @return File directory where contents were unpacked
345 * @throws IOException
346 */
347 protected static File unpack(
348 final File file)
349 throws IOException
350 {
351 return unpack(file, null);
352 }
353
354 /**
355 * Unpacks the archive file.
356 *
357 * @param file File to be unpacked.
358 * @param location Location where to put the unpacked files.
359 * @return File directory where contents were unpacked
360 * @throws IOException
361 */
362 protected static File unpack(
363 final File file,
364 File location)
365 throws IOException
366 {
367 final int ext = file.getAbsolutePath().lastIndexOf('.');
368 if (ext<1)
369 {
370 throw new IOException("File has no extension: " + file.getAbsolutePath());
371 }
372
373 if (location==null || !location.exists())
374 {
375 location = new File(file.getParent() + "/" + StringUtils.remove(file.getName(), '.') + "/");
376 }
377 if (!location.getAbsolutePath().endsWith("/") && !location.getAbsolutePath().endsWith("\\"))
378 {
379 location = new File(location.getAbsolutePath() + "/");
380 }
381 if (!location.exists())
382 {
383 // Create the directory
384 FileUtils.forceMkdir(location);
385 }
386 //final String archiveExt = file.getAbsolutePath().substring(ext+1);
387 //final String archiveExt = FileUtils.getExtension(file.getAbsolutePath()).toLowerCase();
388 try
389 {
390 /*ZipFile zipFile = new ZipFile(file);
391 for (File fileEntry : zipFile.entries())
392 final UnArchiver unArchiver = GlobalPatternFileReplace.archiverManager.getUnArchiver(archiveExt);
393 unArchiver.setSourceFile(file);
394 unArchiver.setDestDirectory(location);
395 unArchiver.extract();
396 }
397 catch (Throwable throwable)
398 {
399 if (throwable instanceof IOException || throwable instanceof ArchiverException)
400 {
401 throw new IOException("Error unpacking file: " + file + "to: " + location, throwable);
402 //}
403 }*/
404 byte[] buf = new byte[1024];
405 ZipInputStream zipinputstream = null;
406 ZipEntry zipentry;
407 zipinputstream = new ZipInputStream(new FileInputStream(file));
408
409 zipentry = zipinputstream.getNextEntry();
410 while (zipentry != null)
411 {
412 //for each entry to be extracted
413 String entryName = zipentry.getName();
414 //System.out.println("entryname "+entryName);
415 FileOutputStream fileoutputstream;
416 File newFile = new File(entryName);
417 String directory = newFile.getParent();
418
419 if(directory == null)
420 {
421 if(newFile.isDirectory())
422 break;
423 }
424
425 File output = new File(location.getAbsolutePath(), entryName);
426 fileoutputstream = new FileOutputStream(output);
427
428 int n;
429 while ((n = zipinputstream.read(buf, 0, 1024)) > -1)
430 fileoutputstream.write(buf, 0, n);
431
432 fileoutputstream.close();
433 zipinputstream.closeEntry();
434 zipentry = zipinputstream.getNextEntry();
435
436 }//while
437
438 zipinputstream.close();
439 }
440 catch (Exception e)
441 {
442 e.printStackTrace();
443 }
444 return location;
445 }
446
447 /**
448 * Writes the given archive file and includes
449 * the file given by the <code>path</code>
450 *
451 * @param zipArchive the zip archive.
452 * @param path the path of the file to write to the zip archive.
453 * @throws IOException
454 */
455 private static void pack(
456 final File zipArchive,
457 final String path)
458 throws IOException
459 {
460 // - retrieve the name of the file given by the path.
461 /*final String name = path.replaceAll(
462 PATH_REMOVE_PATTERN,
463 "");*/
464 //final String name = zipArchive.getName();
465 final ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(zipArchive));
466 File pathFile = new File(path);
467 final ZipEntry zipEntry = new ZipEntry(pathFile.getName());
468 zipEntry.setMethod(ZipEntry.DEFLATED);
469 zipOutputStream.putNextEntry(zipEntry);
470 final FileInputStream inputStream = new FileInputStream(path);
471 final byte[] buffer = new byte[1024];
472 int n = 0;
473 while ((n =
474 inputStream.read(
475 buffer,
476 0,
477 buffer.length)) > 0)
478 {
479 zipOutputStream.write(
480 buffer,
481 0,
482 n);
483 }
484 inputStream.close();
485 zipOutputStream.closeEntry();
486 zipOutputStream.close();
487 }
488
489 /**
490 * Replaces values within a regex match for each replacement in replacements list
491 *
492 * @param contents File contents string
493 * @param replacement The pattern, replace, replaceWith, and deleteStr values
494 */
495 private static String replaceInPattern(String contents)
496 {
497 // apply replacements
498 for (Replacement replacement : replacements)
499 {
500 // contents = contents.replaceAll(replacement.regex, replacement.replacementStr);
501 contents = replaceInPattern(contents, replacement);
502 }
503 return contents;
504 }
505
506 /**
507 * Replaces values within a regex match
508 *
509 * @param contents File contents string
510 * @param replacement The pattern, replace, replaceWith, and deleteStr values
511 */
512 private static String replaceInPattern(String contents, Replacement replacement)
513 {
514 // StringBuffer contentBuffer = new StringBuffer(contents);
515 Matcher matcher = replacement.pattern.matcher(contents);
516 StringBuffer sb = new StringBuffer();
517 while (matcher.find())
518 {
519 boolean ignoreString = false;
520 String toReplace = contents.substring(matcher.start(), matcher.end());
521 for (String ignorePattern : replacement.ignorePatterns)
522 {
523 if (toReplace.indexOf(ignorePattern) > -1)
524 {
525 ignoreString = true;
526 break;
527 }
528 }
529 if (!ignoreString)
530 {
531 toReplace = toReplace.replace(replacement.replaceStr, replacement.replaceWithStr);
532 toReplace = StringUtils.remove(toReplace, replacement.deleteStr);
533 matcher.appendReplacement(sb, toReplace);
534 fileReplacements++;
535 }
536 }
537 sb = matcher.appendTail(sb);
538 return sb.toString();
539 }
540
541 /**
542 * Parses a comma separated list, entering each value into the results Collection. Trailing empty strings are included in the results
543 * Collection.
544 *
545 * @param string string to parse
546 * @param results Collection to which each value will be added
547 */
548 private static Set<String> parseCommaSeparatedList(String string, Set<String> results)
549 {
550 String[] components = string.split(",", -1);
551 if (results==null)
552 {
553 results = new HashSet<String>();
554 }
555 for (int i = 0; i < components.length; i++)
556 {
557 results.add(components[i]);
558 }
559 return results;
560 }
561
562 /**
563 * Reads a mapping from a resource file, and populates a List of Replacement objects. We don't use a Map because the order in which the
564 * replacements are applied can be important.
565 *
566 * @param fileName
567 * name of file to read from (looked up looking using Class.getResource())
568 * @param mappings
569 * List to which Replacement objects will be added. Each object contains the regex to search for and the replacement string.
570 * @param treatAsPackageNames
571 * if true, the keys in the resource file will be considered package names, and this routine will produce regexes that
572 * replace any fully-qualified class name belonging to that package. Also in this case updates the static ibmPackageNames
573 * HashSet.
574 */
575 private static void readMapping(String fileName, List mappings, boolean treatAsPackageNames) throws IOException
576 {
577 // Placeholder until txt file is built/read
578 //List<String> ignorePatterns = new ArrayList<String>();
579 //ignorePatterns.add(".cvs.");
580 // Find pattern @andromda. followed by lowercase letters followed by period (repeating groups) followed by lowercase letter
581 Replacement replacement = new Replacement("@andromda.([a-z]+[\\\\.])+[a-z]", ".", "_", "@", parseCommaSeparatedList(".cvs.", null));
582 GlobalPatternFileReplace.replacements.add(replacement);
583
584 /*URL pkgListFile = AtAndromdaCleanup.class.getResource(fileName);
585 InputStream inStream = pkgListFile.openStream();
586 BufferedReader reader = new BufferedReader(new InputStreamReader(inStream));
587 String line = reader.readLine();
588 while (line != null)
589 {
590 String[] mapping = line.split(" ");
591 String regex, replaceStr;
592 if (treatAsPackageNames)
593 {
594 // we do special processing for package names to try to handle the case where
595 // user code exists in a package prefixed by com.ibm.uima.
596 // We only replace the package name when it appears as part of a fully-qualified
597 // class name in that package, not as a prefix of another package.
598
599 // turn package name into a regex (have to escape the . and,
600 // technically, should allow whitepsace around dots)
601 String pkgRegex = mapping[0].replaceAll("\\.", "\\\\s*\\\\.\\\\s*");
602 // form regex that will find any fully-qualified class name in this package
603 regex = pkgRegex + "(\\.(\\*|[A-Z]\\w*))";
604 replaceStr = mapping[1] + "$1";
605 // ibmPackageNames.add(mapping[0]);
606 }
607 else
608 {
609 // form regex from src, by escaping dots and allowing whitespace
610 regex = mapping[0].replaceAll("\\.", "\\\\s*\\\\.\\\\s*");
611 replaceStr = mapping[1];
612 }
613
614 // Regex for @andromda.([a-z]+[\.])+ matches @andromda. plus repeating sequence of lowercase letters followed by period
615 // replace . with _ in all occurrences within the matched pattern result String, remove @
616 List<String> ignorePatterns = new ArrayList<String>();
617 ignorePatterns.add(".cvs.");
618 Replacement replacement = new Replacement("@andromda.([a-z]+[\\\\.])+", ".", "_", "@", ignorePatterns);
619 // Replacement replacement = new Replacement(regex, replaceStr);
620 mappings.add(replacement);
621 line = reader.readLine();
622 }
623 inStream.close();*/
624 }
625
626 /**
627 * Replace all occurrences of replacement String with replaceWithString inside regex result Delete all occurrences of deleteStr inside
628 * regex result
629 *
630 * @author RJF3
631 */
632 private static class Replacement
633 {
634 String regex;
635 String deleteStr;
636 String replaceStr;
637 String replaceWithStr;
638 Set<String> ignorePatterns;
639 Pattern pattern;
640
641 /**
642 * @param regexIn Input regular expression
643 * @param replacement String to replace text within the existing string
644 * @param replaceWithIn String to replace with
645 * @param deleteStrIn String to delete within the existing String
646 * @param ignorePatternsIn If one of the patterns exists in the existing String, skip replacements
647 */
648 Replacement(String regexIn, String replacement, String replaceWithIn, String deleteStrIn, Set<String> ignorePatternsIn)
649 {
650 this.regex = regexIn;
651 this.replaceStr = replacement;
652 this.deleteStr = deleteStrIn;
653 this.replaceWithStr = replaceWithIn;
654 this.ignorePatterns = ignorePatternsIn;
655 this.pattern = Pattern.compile(regex);
656 }
657 }
658 }