001/**
002 * RenameWand 2.2
003 * Copyright 2007 Zach Scrivena
004 * 2007-12-09
005 * zachscrivena@gmail.com
006 * http://renamewand.sourceforge.net/
007 *
008 * RenameWand is a simple command-line utility for renaming files or
009 * directories using an intuitive but powerful syntax.
010 *
011 * TERMS AND CONDITIONS:
012 * This program is free software: you can redistribute it and/or modify
013 * it under the terms of the GNU General Public License as published by
014 * the Free Software Foundation, either version 3 of the License, or
015 * (at your option) any later version.
016 *
017 * This program is distributed in the hope that it will be useful,
018 * but WITHOUT ANY WARRANTY; without even the implied warranty of
019 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
020 * GNU General Public License for more details.
021 *
022 * You should have received a copy of the GNU General Public License
023 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
024 */
025
026package ca.bc.webarts.tools.renamewand;
027
028import java.io.File;
029import java.text.SimpleDateFormat;
030import java.util.Date;
031import java.util.Locale;
032import java.util.Random;
033
034
035/**
036 * Represent a (candidate) matched file/directory.
037 */
038class FileUnit
039{
040  /** source file/directory (absolute and canonical abstract pathname) */
041  File source = null;
042
043  /** parent directory identifier */
044  int parentDirId = -1;
045
046  /** global file/directory count */
047  int globalCount = -1;
048
049  /** local file/directory count */
050  int localCount = -1;
051
052  /** register values */
053  String[] registerValues = null;
054
055  /** target file/directory name */
056  StringBuilder targetFilename = null;
057
058  /** target file/directory (absolute and canonical abstract pathname) */
059  File target = null;
060
061
062  /**
063   * Evaluate the specified macro, by returning the value of the specified
064   * macro or assigned register corresponding to this file/directory.
065   *
066   * @param macro
067   *     Macro or register name
068   * @return
069   *     String representation of the macro or assigned register value;
070   *     null if macro or register is invalid or unassigned
071   */
072  String evaluateMacro(
073      final String macro)
074  {
075    /************************
076     * (1) FILE NAME MACROS *
077     ************************/
078
079    /* Example: "C:\Work\2007\Jan\Report.txt", with "C:\Work" as current directory */
080
081    /* Filename ("Report.txt") */
082    if ("FN".equals(macro))
083    {
084      String name = this.source.getName();
085
086      /* trim off trailing separator */
087      while (name.endsWith(File.separator))
088        name = name.substring(0, name.length() - File.separator.length());
089
090      return name;
091    }
092
093    /* File extension ("txt") */
094    if ("FN.ext".equals(macro))
095    {
096      String name = this.source.getName();
097
098      /* trim off trailing separator */
099      while (name.endsWith(File.separator))
100        name = name.substring(0, name.length() - File.separator.length());
101
102      final int i = name.lastIndexOf(".");
103
104      if (i < 0)
105      {
106        return "";
107      }
108      else
109      {
110        return name.substring(i + 1);
111      }
112    }
113
114    /* Base filename without extension ("Report") */
115    if ("FN.name".equals(macro))
116    {
117      String name = this.source.getName();
118
119      /* trim off trailing separator */
120      while (name.endsWith(File.separator))
121        name = name.substring(0, name.length() - File.separator.length());
122
123      final int i = name.lastIndexOf(".");
124
125      if (i < 0)
126      {
127        return name;
128      }
129      else
130      {
131        return name.substring(0, i);
132      }
133    }
134
135    /* Relative pathname ("2007\Jan\Report.txt") */
136    if ("FN.path".equals(macro))
137    {
138      String path = this.source.getPath();
139
140      if (path.startsWith(RenameWand.currentDirectoryFullPathname))
141        path = path.substring(RenameWand.currentDirectoryFullPathnameLength);
142
143      /* trim off trailing separator */
144      while (path.endsWith(File.separator))
145        path = path.substring(0, path.length() - File.separator.length());
146
147      return path;
148    }
149
150    /* Parent directory ("Jan") */
151    if ("FN.parent".equals(macro))
152    {
153      final File parent = this.source.getParentFile();
154
155      if ((parent == null) || (parent.equals(RenameWand.currentDirectory)))
156      {
157        return "";
158      }
159      else
160      {
161        String name = parent.getName();
162
163        /* trim off trailing separator */
164        while (name.endsWith(File.separator))
165          name = name.substring(0, name.length() - File.separator.length());
166
167        return name;
168      }
169    }
170
171    /* Relative pathname of parent directory ("2007\Jan") */
172    if ("FN.parentpath".equals(macro))
173    {
174      final File parent = this.source.getParentFile();
175
176      if ((parent == null) || (parent.equals(RenameWand.currentDirectory)))
177      {
178        return "";
179      }
180      else
181      {
182        String path = parent.getPath();
183
184        if (path.startsWith(RenameWand.currentDirectoryFullPathname))
185          path = path.substring(RenameWand.currentDirectoryFullPathnameLength);
186
187        /* trim off trailing separator */
188        while (path.endsWith(File.separator))
189          path = path.substring(0, path.length() - File.separator.length());
190
191        return path;
192      }
193    }
194
195
196    /************************
197     * (2) FILE SIZE MACROS *
198     ************************/
199
200    /* all values are cast as integers */
201
202    /* File size in bytes */
203    if ("FS".equals(macro))
204      return this.source.length() + "";
205
206    /* File size in kilobytes (2^10 bytes) */
207    if ("FS.kB".equals(macro))
208      return (this.source.length() / 1024L) + "";
209
210    /* File size in megabytes (2^20 bytes) */
211    if ("FS.MB".equals(macro))
212      return (this.source.length() / 1048576L) + "";
213
214    /* File size in gigabytes (2^30 bytes) */
215    if ("FS.GB".equals(macro))
216      return (this.source.length() / 1073741824L) + "";
217
218    /* File size in terabytes (2^40 bytes) */
219    if ("FS.TB".equals(macro))
220      return (this.source.length() / 1099511627776L) + "";
221
222
223    /**************************************
224     * (3) FILE LAST-MODIFIED TIME MACROS *
225     **************************************/
226
227    /* Number of milliseconds since the epoch (January 1, 1970 00:00:00 GMT, Gregorian) */
228    if ("FT".equals(macro))
229      return this.source.lastModified() + "";
230
231    /* Date in the form yyyyMMdd */
232    if ("FT.date".equals(macro))
233      return (new SimpleDateFormat("yyyyMMdd", Locale.ENGLISH)).format(new Date(this.source.lastModified()));
234
235    /* Time in the form HHmmss */
236    if ("FT.time".equals(macro))
237      return (new SimpleDateFormat("HHmmss", Locale.ENGLISH)).format(new Date(this.source.lastModified()));
238
239    /* am/pm in lower case */
240    if ("FT.ap".equals(macro))
241      return (new SimpleDateFormat("a", Locale.ENGLISH)).format(new Date(this.source.lastModified())).toLowerCase(Locale.ENGLISH);
242
243    /* AM/PM in upper case */
244    if ("FT.AP".equals(macro))
245      return (new SimpleDateFormat("a", Locale.ENGLISH)).format(new Date(this.source.lastModified())).toUpperCase(Locale.ENGLISH);
246
247    /* Date and time pattern letters from Java */
248    if (macro.startsWith("FT."))
249    {
250      final String macroName = macro.substring(macro.indexOf(".") + 1);
251
252      if (macroName.length() > 0)
253      {
254        boolean validChars = true;
255
256        for (int i = 0; i < macroName.length(); i++)
257        {
258          if (!"GyMwWDdFEaHkKhmsSzZ".contains(macroName.charAt(i) + ""))
259          {
260            validChars = false;
261            break;
262          }
263        }
264
265        if (validChars)
266          return (new SimpleDateFormat(macroName, Locale.ENGLISH)).format(new Date(this.source.lastModified()));
267      }
268    }
269
270
271    /***************************
272     * (4) CURRENT TIME MACROS *
273     ***************************/
274
275    /* Number of milliseconds since the epoch (January 1, 1970 00:00:00 GMT, Gregorian) */
276    if ("CT".equals(macro))
277      return (new Date()).getTime() + "";
278
279    /* Date in the form yyyyMMdd */
280    if ("CT.date".equals(macro))
281      return (new SimpleDateFormat("yyyyMMdd", Locale.ENGLISH)).format(new Date());
282
283    /* Time in the form HHmmss */
284    if ("CT.time".equals(macro))
285      return (new SimpleDateFormat("HHmmss", Locale.ENGLISH)).format(new Date());
286
287    /* am/pm in lower case */
288    if ("CT.ap".equals(macro))
289      return (new SimpleDateFormat("a", Locale.ENGLISH)).format(new Date()).toLowerCase(Locale.ENGLISH);
290
291    /* AM/PM in upper case */
292    if ("CT.AP".equals(macro))
293      return (new SimpleDateFormat("a", Locale.ENGLISH)).format(new Date()).toUpperCase(Locale.ENGLISH);
294
295    /* Date and time pattern letters from Java */
296    if (macro.startsWith("CT."))
297    {
298      final String macroName = macro.substring(macro.indexOf(".") + 1);
299
300      if (macroName.length() > 0)
301      {
302        boolean validChars = true;
303
304        for (int i = 0; i < macroName.length(); i++)
305        {
306          if (!"GyMwWDdFEaHkKhmsSzZ".contains(macroName.charAt(i) + ""))
307          {
308            validChars = false;
309            break;
310          }
311        }
312
313        if (validChars)
314          return (new SimpleDateFormat(macroName, Locale.ENGLISH)).format(new Date());
315      }
316    }
317
318
319    /*******************************************
320     * (5) SYSTEM ENVIRONMENT VARIABLES MACROS *
321     *******************************************/
322
323    if (macro.startsWith("ENV."))
324    {
325      final String macroName = macro.substring(macro.indexOf(".") + 1);
326
327      if (macroName.length() > 0)
328      {
329        final String value = System.getenv(macroName);
330
331        if (value != null)
332          return value;
333      }
334    }
335
336
337    /********************************
338     * (6) SYSTEM PROPERTIES MACROS *
339     ********************************/
340
341    if (macro.startsWith("SYS."))
342    {
343      final String macroName = macro.substring(macro.indexOf(".") + 1);
344
345      if (macroName.length() > 0)
346      {
347        final String value = System.getProperty(macroName);
348
349        if (value != null)
350          return value;
351      }
352    }
353
354
355    /****************************
356     * (7) MISCELLANEOUS MACROS *
357     ****************************/
358
359    /* Full pathname of the current directory (e.g. "C:\Work") */
360    if ("RW.cd".equals(macro))
361    {
362      String path = RenameWand.currentDirectoryFullPathname;
363
364      /* trim off trailing separator */
365      while (path.endsWith(File.separator))
366        path = path.substring(0, path.length() - File.separator.length());
367
368      return path;
369    }
370
371    /* Number of local matched files (i.e. in the file's subdirectory) */
372    if ("RW.N".equals(macro) && (this.localCount > 0))
373      return this.localCount + "";
374
375    /* Number of global matched files (i.e. in all subdirectories) */
376    if ("RW.NN".equals(macro) && (this.globalCount > 0))
377      return this.globalCount + "";
378
379    /* Generate a string of 10 random digits */
380    if ("RW.random".equals(macro))
381    {
382      /* return a string of 10 random digits (0-9) */
383      final Random rng = new Random();
384      final StringBuilder randomString = new StringBuilder();
385
386      /* append a digit */
387      while (randomString.length() < 10)
388        randomString.append(rng.nextInt(10));
389
390      return randomString.substring(0, 10);
391    }
392
393
394    /***********************************************
395     * (8) MACRO/REGISTER MODIFIERS AND ATTRIBUTES *
396     ***********************************************/
397
398    /* Example: "hello WORLD" */
399
400    /* Length of the string (11) */
401    if (macro.endsWith(".len"))
402    {
403      final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
404      final String parentMacroValue = this.evaluateMacro(parentMacro);
405
406      if (parentMacroValue != null)
407        return parentMacroValue.length() + "";
408    }
409
410    /* Convert to upper case ("HELLO WORLD") */
411    if (macro.endsWith(".upper"))
412    {
413      final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
414      final String parentMacroValue = this.evaluateMacro(parentMacro);
415
416      if (parentMacroValue != null)
417        return parentMacroValue.toUpperCase();
418    }
419
420    /* Convert to lower case ("hello world") */
421    if (macro.endsWith(".lower"))
422    {
423      final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
424      final String parentMacroValue = this.evaluateMacro(parentMacro);
425
426      if (parentMacroValue != null)
427        return parentMacroValue.toLowerCase();
428    }
429
430    /* Capitalize only the first character ("Hello world") */
431    if (macro.endsWith(".capitalize"))
432    {
433      final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
434      final String parentMacroValue = this.evaluateMacro(parentMacro);
435
436      if (parentMacroValue != null)
437      {
438        if (parentMacroValue.isEmpty())
439          return parentMacroValue;
440
441        return Character.toUpperCase(parentMacroValue.charAt(0)) +
442            parentMacroValue.substring(1).toLowerCase();
443      }
444    }
445
446    /* Convert to title case ("Hello World") */
447    if (macro.endsWith(".title"))
448    {
449      final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
450      final String parentMacroValue = this.evaluateMacro(parentMacro);
451
452      if (parentMacroValue != null)
453        return StringManipulator.toTitleCase(parentMacroValue);
454    }
455
456    /* Convert to camelCase ("helloWorld") */
457    if (macro.endsWith(".camel"))
458    {
459      final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
460      final String parentMacroValue = this.evaluateMacro(parentMacro);
461
462      if (parentMacroValue != null)
463      {
464        final String value = StringManipulator.deleteWhitespace(StringManipulator.toTitleCase(parentMacroValue));
465
466        if (value.isEmpty())
467          return value;
468
469        return Character.toLowerCase(value.charAt(0)) + value.substring(1);
470      }
471    }
472
473    /* Convert to PascalCase ("HelloWorld") */
474    if (macro.endsWith(".pascal"))
475    {
476      final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
477      final String parentMacroValue = this.evaluateMacro(parentMacro);
478
479      if (parentMacroValue != null)
480        return StringManipulator.deleteWhitespace(StringManipulator.toTitleCase(parentMacroValue));
481    }
482
483    /* Swap the case ("HELLO world") */
484    if (macro.endsWith(".swapcase"))
485    {
486      final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
487      final String parentMacroValue = this.evaluateMacro(parentMacro);
488
489      if (parentMacroValue != null)
490        return StringManipulator.swapCase(parentMacroValue);
491    }
492
493    /* Abbreviate to initials ("h W") */
494    if (macro.endsWith(".abbrev"))
495    {
496      final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
497      final String parentMacroValue = this.evaluateMacro(parentMacro);
498
499      if (parentMacroValue != null)
500        return StringManipulator.abbreviate(parentMacroValue);
501    }
502
503    /* Reverse the string ("DLROW olleh") */
504    if (macro.endsWith(".reverse"))
505    {
506      final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
507      final String parentMacroValue = this.evaluateMacro(parentMacro);
508
509      if (parentMacroValue != null)
510        return StringManipulator.reverse(parentMacroValue);
511    }
512
513    /* Trim away whitespace on the left and right */
514    if (macro.endsWith(".trim"))
515    {
516      final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
517      final String parentMacroValue = this.evaluateMacro(parentMacro);
518
519      if (parentMacroValue != null)
520        return parentMacroValue.trim();
521    }
522
523    /* Trim away whitespace on the left */
524    if (macro.endsWith(".ltrim"))
525    {
526      final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
527      final String parentMacroValue = this.evaluateMacro(parentMacro);
528
529      if (parentMacroValue != null)
530        return StringManipulator.leftTrim(parentMacroValue);
531    }
532
533    /* Trim away whitespace on the right */
534    if (macro.endsWith(".rtrim"))
535    {
536      final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
537      final String parentMacroValue = this.evaluateMacro(parentMacro);
538
539      if (parentMacroValue != null)
540        return StringManipulator.rightTrim(parentMacroValue);
541    }
542
543    /* Delete whitespace in the string */
544    if (macro.endsWith(".delspace"))
545    {
546      final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
547      final String parentMacroValue = this.evaluateMacro(parentMacro);
548
549      if (parentMacroValue != null)
550        return StringManipulator.deleteWhitespace(parentMacroValue);
551    }
552
553    /* Delete extra whitespace by replacing contiguous whitespace with a single space */
554    if (macro.endsWith(".delextraspace"))
555    {
556      final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
557      final String parentMacroValue = this.evaluateMacro(parentMacro);
558
559      if (parentMacroValue != null)
560        return StringManipulator.deleteExtraWhitespace(parentMacroValue);
561    }
562
563    /* Delete punctuation marks in the string */
564    if (macro.endsWith(".delpunctuation"))
565    {
566      final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
567      final String parentMacroValue = this.evaluateMacro(parentMacro);
568
569      if (parentMacroValue != null)
570        return StringManipulator.deletePunctuation(parentMacroValue);
571    }
572
573    /* Space out words by inserting a space between connected words */
574    if (macro.endsWith(".spaceout"))
575    {
576      final String parentMacro = macro.substring(0, macro.lastIndexOf("."));
577      final String parentMacroValue = this.evaluateMacro(parentMacro);
578
579      if (parentMacroValue != null)
580        return StringManipulator.spaceOutWords(parentMacroValue);
581    }
582
583
584    /****************************
585     * (9) REGISTERS (ASSIGNED) *
586     ****************************/
587
588    /* MUST BE THIRD-LAST BLOCK */
589
590    if ((this.registerValues != null) &&
591        RenameWand.registerNames.containsKey(macro))
592    {
593      return this.registerValues[RenameWand.registerNames.get(macro)];
594    }
595
596
597    /***************************************************
598     * (10) SHORTCUTS FOR SINGLE-LETTER REGISTER NAMES *
599     ***************************************************/
600
601    /* MUST BE SECOND-LAST BLOCK */
602
603    /* Example: a ---> aa AA Aa aA
604     * Rule: If name is not a register, and is two chars long,
605     * and first char == second char (ignore case), and exclusively
606     * either small or big letter is a register name, then return
607     * .lower, .upper, .title, .swapcase
608     */
609    if ((this.registerValues != null) &&
610        (macro.length() == 2))
611    {
612      final String c1 = macro.charAt(0) + "";
613      final String c2 = macro.charAt(1) + "";
614
615      if (c1.equalsIgnoreCase(c2))
616      {
617        final String upper = c1.toUpperCase(Locale.ENGLISH);
618        final String lower = c1.toLowerCase(Locale.ENGLISH);
619        final boolean upperIsRegister = RenameWand.registerNames.containsKey(upper);
620        final boolean lowerIsRegister = RenameWand.registerNames.containsKey(lower);
621        String parentMacro = null;
622
623        if (upperIsRegister && !lowerIsRegister)
624        {
625          parentMacro = upper;
626        }
627        else if (lowerIsRegister && !upperIsRegister)
628        {
629          parentMacro = lower;
630        }
631
632        if (parentMacro != null)
633        {
634          if (macro.equals(lower + lower))
635            return this.evaluateMacro(parentMacro + ".lower");
636
637          if (macro.equals(upper + upper))
638            return this.evaluateMacro(parentMacro + ".upper");
639
640          if (macro.equals(upper + lower))
641            return this.evaluateMacro(parentMacro + ".title");
642
643          if (macro.equals(lower + upper))
644            return this.evaluateMacro(parentMacro + ".swapcase");
645        }
646      }
647    }
648
649
650    /***************************
651     * (11) UNIDENTIFIED MACRO *
652     ***************************/
653
654    /* MUST BE LAST BLOCK */
655
656    return null; // this should be the only "return null" in this method
657  }
658}