001/**
002 * From Apache Uima migration utils file at
003 * http://svn.apache.org/repos/asf/incubator/uima/uimaj/trunk/uimaj-tools/src/main/java/org/apache/uima
004 * /tools/migration/IbmUimaToApacheUima.java
005 */
006package org.andromda.utils;
007
008import java.io.File;
009import java.io.FileInputStream;
010import java.io.FileOutputStream;
011import java.io.IOException;
012import java.util.ArrayList;
013import java.util.Collection;
014import java.util.HashSet;
015import java.util.List;
016import java.util.Set;
017import java.util.regex.Matcher;
018import java.util.regex.Pattern;
019import java.util.zip.ZipEntry;
020import java.util.zip.ZipInputStream;
021import java.util.zip.ZipOutputStream;
022import org.apache.commons.io.FileUtils;
023import org.apache.commons.io.filefilter.FileFilterUtils;
024import org.apache.commons.io.filefilter.IOFileFilter;
025import org.apache.commons.lang.StringUtils;
026
027/**
028 * Iterate down through a directory hierarchy, replace @andromda.whatever.whatever with the Java compliant andromda_whatever_whatever tagged
029 * value (attribute) format. Open .zip files if necessary.
030 * 
031 * @author RJF3
032 */
033public class GlobalPatternFileReplace
034{
035    private static List<Replacement> replacements = new ArrayList<Replacement>();
036    private static int MAX_FILE_SIZE = 10000000; // don't update files bigger than 10 MB
037    private static Set<String> extensions = new HashSet<String>();
038    private static int filesScanned = 0;
039    private static int filesModified = 0;
040    private static int fileReplacements = 0;
041
042    /**
043     * 
044     */
045    public GlobalPatternFileReplace()
046    {
047        // TODO Auto-generated constructor stub
048    }
049
050    /**
051     * @param args
052     * @throws IOException 
053     */
054    public static void main(String[] args) throws IOException
055    {
056        File file = new File("C:/workspaces/A34/andromda341/cartridges/andromda-spring/pom.xml");
057        // parse command line
058        String dir = null;
059        for (int i = 0; i < args.length; i++)
060        {
061            if (args[i].startsWith("-"))
062            {
063                if (args[i].equals("-ext"))
064                {
065                    if (i + 1 >= args.length)
066                    {
067                        printUsageAndExit();
068                    }
069                    parseCommaSeparatedList(args[++i], extensions);
070                }
071                else
072                {
073                    System.err.println("Unknown switch " + args[i]);
074                    printUsageAndExit();
075                }
076            }
077            else
078            {
079                if (dir != null)
080                {
081                    printUsageAndExit();
082                }
083                else
084                {
085                    dir = args[i];
086                    file = new File(dir);
087                }
088            }
089        }
090        /*if (dir == null)
091        {
092            //file = new File(".");
093            //System.out.println("Using default current directory: " + file.getCanonicalPath());
094            file = new File("C:/workspaces/A34/andromda341/cartridges/andromda-spring");
095            //printUsageAndExit();
096        }
097        if (file == null)
098        {
099            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}