001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.theme.util;
017
018import org.apache.commons.lang3.StringUtils;
019import org.codehaus.plexus.util.DirectoryScanner;
020import org.codehaus.plexus.util.FileUtils;
021import org.codehaus.plexus.util.SelectorUtils;
022
023import java.io.File;
024import java.io.FileInputStream;
025import java.io.FileWriter;
026import java.io.IOException;
027import java.util.ArrayList;
028import java.util.List;
029import java.util.Properties;
030
031/**
032 * Utility methods for the view builder module
033 *
034 * @author Kuali Rice Team (rice.collab@kuali.org)
035 */
036public class ThemeBuilderUtils {
037
038    /**
039     * Retrieve the {@link Properties} object loaded from the theme.properties file found in the given
040     * theme directory
041     *
042     * @param themeDirectory directory for the theme to pull properties file from
043     * @return Properties object loaded with the theme configuration, or null if the properties file
044     *         does not exist
045     * @throws IOException
046     */
047    public static Properties retrieveThemeProperties(String themeDirectory) throws IOException {
048        Properties themeProperties = null;
049
050        FileInputStream fileInputStream = null;
051
052        try {
053            File propertiesFile = new File(themeDirectory, ThemeBuilderConstants.THEME_PROPERTIES_FILE);
054
055            if (propertiesFile.exists()) {
056                fileInputStream = new FileInputStream(propertiesFile);
057
058                themeProperties = new Properties();
059                themeProperties.load(fileInputStream);
060            }
061        } finally {
062            if (fileInputStream != null) {
063                fileInputStream.close();
064            }
065        }
066
067        return themeProperties;
068    }
069
070    /**
071     * Stores the given properties object in a file named <code>theme-derived.properties</code> within the
072     * given theme directory
073     *
074     * @param themeDirectory directory the properties file should be created in
075     * @param themeProperties properties that should be written to the properties file
076     * @throws IOException
077     */
078    public static void storeThemeProperties(String themeDirectory, Properties themeProperties) throws IOException {
079        File propertiesFile = new File(themeDirectory, ThemeBuilderConstants.THEME_DERIVED_PROPERTIES_FILE);
080
081        // need to remove file if already exists so the new properties will be written
082        if (propertiesFile.exists()) {
083            FileUtils.forceDelete(propertiesFile);
084        }
085
086        FileWriter fileWriter = null;
087
088        try {
089            fileWriter = new FileWriter(propertiesFile);
090
091            themeProperties.store(fileWriter, null);
092        } finally {
093            if (fileWriter != null) {
094                fileWriter.close();
095            }
096        }
097    }
098
099    /**
100     * Retrieves the value for the property with the given key from the properties object, as a list of
101     * strings (by splitting the value on commas)
102     *
103     * @param key key for the property to retrieve
104     * @param properties properties object to pull property from
105     * @return list of strings parsed from the property value
106     */
107    public static List<String> getPropertyValueAsList(String key, Properties properties) {
108        List<String> propertyValueList = null;
109
110        String[] propertyValueArray = getPropertyValueAsArray(key, properties);
111        if (propertyValueArray != null) {
112            propertyValueList = new ArrayList<String>();
113
114            for (String value : propertyValueArray) {
115                propertyValueList.add(value);
116            }
117        }
118
119        return propertyValueList;
120    }
121
122    /**
123     * Retrieves the value for the property with the given key from the properties object, as an array of
124     * strings (by splitting the value on commas)
125     *
126     * @param key key for the property to retrieve
127     * @param properties properties object to pull property from
128     * @return array of strings parsed from the property value
129     */
130    public static String[] getPropertyValueAsArray(String key, Properties properties) {
131        String[] propertyValue = null;
132
133        if (properties.containsKey(key)) {
134            String propertyValueString = properties.getProperty(key);
135
136            if (!StringUtils.isBlank(propertyValueString)) {
137                propertyValue = propertyValueString.split(",");
138            }
139        }
140
141        return propertyValue;
142    }
143
144    /**
145     * Copies the property key/value from the source properties to the target properties if a property with the
146     * same key does not exist in the target properties
147     *
148     * @param propertyKey key of the property to copy
149     * @param sourceProperties properties to pull the property from
150     * @param targetProperties properties to copy the property to
151     */
152    public static void copyProperty(String propertyKey, Properties sourceProperties, Properties targetProperties) {
153        if (targetProperties != null && targetProperties.containsKey(propertyKey) && StringUtils.isNotBlank(
154                targetProperties.getProperty(propertyKey))) {
155            return;
156        }
157
158        if (sourceProperties != null && sourceProperties.containsKey(propertyKey)) {
159            String propertyValue = sourceProperties.getProperty(propertyKey);
160
161            if (targetProperties == null) {
162                targetProperties = new Properties();
163            }
164
165            targetProperties.put(propertyKey, propertyValue);
166        }
167    }
168
169    /**
170     * Iterates through each file in the given list and verifies the file exists, if not a runtime
171     * exception is thrown with the provided message
172     *
173     * @param filesToValidate list of files to check existence for
174     * @param exceptionMessage message for runtime exception if a file is found that does not exist
175     */
176    public static void validateFileExistence(List<File> filesToValidate, String exceptionMessage) {
177        if (filesToValidate == null) {
178            return;
179        }
180
181        for (File file : filesToValidate) {
182            if (!file.exists()) {
183                throw new RuntimeException(exceptionMessage + " Path: " + file.getPath());
184            }
185        }
186    }
187
188    /**
189     * Indicates whether there is a file with the given name within the given directory
190     *
191     * @param directory directory to check for file
192     * @param fileName name of the file to check for
193     * @return true if there is a file in the directory, false if not
194     */
195    public static boolean directoryContainsFile(File directory, String fileName) {
196        boolean containsFile = false;
197
198        List<String> directoryContents = getDirectoryContents(directory, null, null);
199
200        for (String directoryFile : directoryContents) {
201            String directoryFilename = FileUtils.filename(directoryFile);
202
203            if (directoryFilename.equals(fileName)) {
204                containsFile = true;
205            }
206        }
207
208        return containsFile;
209    }
210
211    /**
212     * Retrieves a list of files that are in the given directory, possibly filtered by the list of include
213     * patterns or exclude patterns
214     *
215     * @param baseDirectory directory to retrieve files from
216     * @param includes list of patterns to match against for files to include, can include Ant patterns
217     * @param excludes list of patterns to match for excluded files, can include Ant patterns
218     * @return list of files within the directory that match all given patterns
219     */
220    public static List<File> getDirectoryFiles(File baseDirectory, String[] includes, String[] excludes) {
221        List<File> directoryFiles = new ArrayList<File>();
222
223        List<String> directoryFileNames = getDirectoryFileNames(baseDirectory, includes, excludes);
224
225        for (String fileName : directoryFileNames) {
226            directoryFiles.add(new File(baseDirectory, fileName));
227        }
228
229        return directoryFiles;
230    }
231
232    /**
233     * Retrieves a list of file names that are in the given directory, possibly filtered by the list of include
234     * patterns or exclude patterns
235     *
236     * @param baseDirectory directory to retrieve file names from
237     * @param includes list of patterns to match against for file names to include, can include Ant patterns
238     * @param excludes list of patterns to match for excluded file names, can include Ant patterns
239     * @return list of file names within the directory that match all given patterns
240     */
241    public static List<String> getDirectoryFileNames(File baseDirectory, String[] includes, String[] excludes) {
242        List<String> files = new ArrayList<String>();
243
244        DirectoryScanner scanner = new DirectoryScanner();
245
246        if (includes != null) {
247            scanner.setIncludes(includes);
248        }
249
250        if (excludes != null) {
251            scanner.setExcludes(excludes);
252        }
253
254        scanner.setCaseSensitive(false);
255        scanner.addDefaultExcludes();
256        scanner.setBasedir(baseDirectory);
257
258        scanner.scan();
259
260        for (String includedFilename : scanner.getIncludedFiles()) {
261            files.add(includedFilename);
262        }
263
264        return files;
265    }
266
267    /**
268     * Get the sub directories of the given directory that have the given names
269     *
270     * @param baseDirectory directory containing the sub directories
271     * @param subDirectoryNames list of sub directory names to return
272     * @return list of Files pointing to the sub directories
273     */
274    public static List<File> getSubDirectories(File baseDirectory, List<String> subDirectoryNames) {
275        List<File> subDirs = null;
276
277        if (subDirectoryNames != null) {
278            subDirs = new ArrayList<File>();
279
280            for (String pluginName : subDirectoryNames) {
281                subDirs.add(new File(baseDirectory, pluginName));
282            }
283        }
284
285        return subDirs;
286    }
287
288    /**
289     * Retrieves a list of files and directories that are in the given directory, possibly filtered by the
290     * list of include patterns or exclude patterns
291     *
292     * @param baseDirectory directory to retrieve files and directories from
293     * @param includes list of patterns to match against for files to include, can include Ant patterns
294     * @param excludes list of patterns to match for excluded files, can include Ant patterns
295     * @return list of files within the directory that match all given patterns
296     */
297    public static List<String> getDirectoryContents(File baseDirectory, String[] includes, String[] excludes) {
298        List<String> contents = new ArrayList<String>();
299
300        DirectoryScanner scanner = new DirectoryScanner();
301
302        if (includes != null) {
303            scanner.setIncludes(includes);
304        }
305
306        if (excludes != null) {
307            scanner.setExcludes(excludes);
308        }
309
310        scanner.setCaseSensitive(false);
311        scanner.addDefaultExcludes();
312        scanner.setBasedir(baseDirectory);
313
314        scanner.scan();
315
316        for (String includedDirectory : scanner.getIncludedDirectories()) {
317            contents.add(includedDirectory);
318        }
319
320        for (String includedFilename : scanner.getIncludedFiles()) {
321            contents.add(includedFilename);
322        }
323
324        return contents;
325    }
326
327    /**
328     * Copies all the contents from the directory given by the source path to the directory given by the
329     * target path
330     *
331     * <p>
332     * If source directory does not exist nothing is performed. The target directory will be created if it
333     * does not exist. Any hidden directories (directory names that start with ".") will be deleted from the
334     * target directory
335     * </p>
336     *
337     * @param sourceDirectoryPath absolute path to the source directory
338     * @param targetDirectoryPath absolute path to the target directory
339     * @throws IOException
340     */
341    public static void copyDirectory(String sourceDirectoryPath, String targetDirectoryPath)
342            throws IOException {
343        File sourceDir = new File(sourceDirectoryPath);
344
345        if (!sourceDir.exists()) {
346            return;
347        }
348
349        File targetDir = new File(targetDirectoryPath);
350        if (targetDir.exists()) {
351            // force removal so the copy starts clean
352            FileUtils.forceDelete(targetDir);
353        }
354
355        targetDir.mkdir();
356
357        FileUtils.copyDirectoryStructure(sourceDir, targetDir);
358
359        // remove hidden directories from the target
360        DirectoryScanner scanner = new DirectoryScanner();
361        scanner.setBasedir(targetDir);
362
363        scanner.scan();
364
365        for (String includedDirectory : scanner.getIncludedDirectories()) {
366            File subdirectory = new File(targetDir, includedDirectory);
367
368            if (subdirectory.exists() && subdirectory.isDirectory()) {
369                if (subdirectory.getName().startsWith(".")) {
370                    FileUtils.forceDelete(subdirectory);
371                }
372            }
373        }
374    }
375
376    /**
377     * Copies all content (files and directories) from the source directory to the target directory, except content
378     * that already exists in the target directory (same name and path), in other words it does not override any
379     * existing content
380     *
381     * <p>
382     * Files from the source directory can be excluded from the copying by setting one or more patterns in the
383     * source excludes list
384     * </p>
385     *
386     * @param sourceDirectory directory to copy content from
387     * @param targetDirectory directory to copy content to
388     * @param sourceExcludes list of patterns to match on for source exclusions
389     * @throws IOException
390     */
391    public static void copyMissingContent(File sourceDirectory, File targetDirectory, List<String> sourceExcludes)
392            throws IOException {
393        String[] copyExcludes = null;
394
395        if ((sourceExcludes != null) && !sourceExcludes.isEmpty()) {
396            copyExcludes = new String[sourceExcludes.size()];
397
398            copyExcludes = sourceExcludes.toArray(copyExcludes);
399        }
400
401        List<String> sourceDirectoryContents = getDirectoryContents(sourceDirectory, null, copyExcludes);
402        List<String> targetDirectoryContents = getDirectoryContents(targetDirectory, null, null);
403
404        for (String sourceContent : sourceDirectoryContents) {
405            if (targetDirectoryContents.contains(sourceContent)) {
406                continue;
407            }
408
409            // copy file to target
410            File sourceFile = new File(sourceDirectory, sourceContent);
411            File targetFile = new File(targetDirectory, sourceContent);
412
413            if (sourceFile.isDirectory()) {
414                targetFile.mkdir();
415            } else {
416                FileUtils.copyFile(sourceFile, targetFile);
417            }
418        }
419    }
420
421    /**
422     * Determines if one of the given patterns matches the given name, or if the include list is null
423     * or empty the file will be included
424     *
425     * @param name string to match
426     * @param includes list of string patterns to match on
427     * @return true if the name is a match, false if not
428     */
429    public static boolean inIncludeList(String name, String[] includes) {
430        if ((includes == null) || (includes.length == 0)) {
431            return true;
432        }
433
434        for (String include : includes) {
435            if (SelectorUtils.matchPath(include, name, false)) {
436                return true;
437            }
438        }
439
440        return false;
441    }
442
443    /**
444     * Determines if one of the given patterns matches the given name, or if the exclude list is null
445     * or empty the file will not be excluded
446     *
447     * @param name string to match
448     * @param excludes list of string patterns to match on
449     * @return true if the name is a match, false if not
450     */
451    public static boolean inExcludeList(String name, String[] excludes) {
452        if ((excludes == null) || (excludes.length == 0)) {
453            return false;
454        }
455
456        for (String exclude : excludes) {
457            if (SelectorUtils.matchPath(exclude, name, false)) {
458                return true;
459            }
460        }
461
462        return false;
463    }
464
465    /**
466     * Iterates through the given list of patterns and checks whether the pattern ends with the given
467     * extension or a wildcard, if not the extension is appended to the pattern
468     *
469     * @param patterns array of patterns to check and append to if necessary
470     * @param extension string extension to check for and append if necessary
471     */
472    public static void addExtensionToPatterns(String[] patterns, String extension) {
473        if (patterns == null) {
474            return;
475        }
476
477        for (int i = 0; i < patterns.length; i++) {
478            String pattern = patterns[i];
479
480            if (!(pattern.endsWith("*") || pattern.endsWith(extension))) {
481                patterns[i] = pattern + extension;
482            }
483        }
484    }
485
486    /**
487     * Builds a list of strings that hold the path from each given file relative to the parent
488     * directory
489     *
490     * @param parentDirectory directory to build path from
491     * @param files list of files to build relative paths for
492     * @return list of strings containing the relative paths
493     */
494    public static List<String> getRelativePaths(File parentDirectory, List<File> files) {
495        List<String> relativePaths = new ArrayList<String>();
496
497        for (File file : files) {
498            relativePaths.add(getRelativePath(parentDirectory, file));
499        }
500
501        return relativePaths;
502    }
503
504    /**
505     * Returns the path of the given file relative to the parent directory
506     *
507     * @param parentDirectory directory to build path from
508     * @param file file to build relative paths for
509     * @return string containing the relative path
510     */
511    public static String getRelativePath(File parentDirectory, File file) {
512        String relativePath = null;
513
514        String parentPath = parentDirectory.getPath();
515        String childPath = file.getPath();
516
517        if (childPath.startsWith(parentPath + File.separator)) {
518            relativePath = childPath.substring(parentPath.length() + 1);
519        }
520
521        // switch path separators
522        relativePath = relativePath.replaceAll("\\\\", "/");
523
524        return relativePath;
525    }
526
527    /**
528     * Calculates the path from the first file to the second
529     *
530     * <p>
531     * Assumes there is a common base directory somewhere in the path of both files. Once it finds that base
532     * directory, builds the path starting at the from file to it, then adds the path from the base directory
533     * to the target file
534     * </p>
535     *
536     * @param fromFile file whose path is the starting point
537     * @param toFile file whose path is the ending point
538     * @return string containing the path
539     */
540    public static String calculatePathToFile(File fromFile, File toFile) {
541        String pathToFile = "";
542
543        int directoriesUp = 0;
544        String parentPath = fromFile.getParent();
545
546        while ((parentPath != null) && !fileMatchesPath(parentPath, toFile)) {
547            File parent = new File(parentPath);
548
549            parentPath = parent.getParent();
550            directoriesUp += 1;
551        }
552
553        if (parentPath != null) {
554            for (int i = 0; i < directoriesUp; i++) {
555                pathToFile += "../";
556            }
557
558            String remainingPath = toFile.getPath().replace(parentPath, "");
559
560            if (remainingPath.startsWith(File.separator)) {
561                remainingPath = remainingPath.substring(1);
562            }
563
564            // switch path separators
565            remainingPath = remainingPath.replaceAll("\\\\", "/");
566
567            // remove file name from path
568            if (remainingPath.contains("/")) {
569                int separatorIndex = remainingPath.lastIndexOf("/");
570                remainingPath = remainingPath.substring(0, separatorIndex + 1);
571            } else {
572                // file in same directory, no remaining path
573                remainingPath = null;
574            }
575
576            if (remainingPath != null) {
577                pathToFile += remainingPath;
578            }
579        }
580
581        return pathToFile;
582    }
583
584    /**
585     * Indicates whether the given file is withing the given path (file's path starts with the given path), note
586     * this does not check whether the file exists
587     *
588     * @param path path to check for
589     * @param file file whose path should be checked
590     * @return true if the file is contained in the path, false if not
591     */
592    protected static boolean fileMatchesPath(String path, File file) {
593        return file.getPath().startsWith(path);
594    }
595
596    /**
597     * Orders the list of plugin files and sub directory files according to the given patterns
598     *
599     * @param pluginFiles list of plugin files to order
600     * @param subDirFiles list of sub directory files to order
601     * @param loadFirstPatterns list of patterns for files that should be ordered first
602     * @param loadLastPatterns list of patterns for files that should be ordered last
603     * @param pluginLoadOrder list of patterns for ordering the plugin files
604     * @param subDirLoadOrder list of patterns for ordering the sub directory files
605     * @return list containing all of the given plugin and sub directory files ordered by the given patterns
606     */
607    public static List<File> orderFiles(List<File> pluginFiles, List<File> subDirFiles, List<String> loadFirstPatterns,
608            List<String> loadLastPatterns, List<String> pluginLoadOrder, List<String> subDirLoadOrder) {
609        List<File> orderedFiles = new ArrayList<File>();
610
611        List<File> allThemeFiles = new ArrayList<File>();
612        if (pluginFiles != null) {
613            allThemeFiles.addAll(pluginFiles);
614        }
615
616        if (subDirFiles != null) {
617            allThemeFiles.addAll(subDirFiles);
618        }
619
620        // build end of the ordered list since those should take priority
621        List<File> endFiles = new ArrayList<File>();
622
623        if (loadLastPatterns != null) {
624            for (String pattern : loadLastPatterns) {
625                endFiles.addAll(matchFiles(allThemeFiles, pattern));
626            }
627        }
628
629        // build beginning of the ordered list
630        if (loadFirstPatterns != null) {
631            for (String pattern : loadFirstPatterns) {
632                List<File> matchedFiles = matchFiles(allThemeFiles, pattern);
633                matchedFiles.removeAll(endFiles);
634
635                orderedFiles.addAll(matchedFiles);
636            }
637        }
638
639        // add plugin files that have been configured to load before other plugin files
640        if (pluginLoadOrder != null) {
641            for (String pattern : pluginLoadOrder) {
642                List<File> matchedFiles = matchFiles(pluginFiles, pattern);
643                matchedFiles.removeAll(endFiles);
644                matchedFiles.removeAll(orderedFiles);
645
646                orderedFiles.addAll(matchedFiles);
647            }
648        }
649
650        // add remaining plugin files
651        if (pluginFiles != null) {
652            for (File pluginFile : pluginFiles) {
653                if (!orderedFiles.contains(pluginFile) && !endFiles.contains(pluginFile)) {
654                    orderedFiles.add(pluginFile);
655                }
656            }
657        }
658
659        // add sub dir files that have been configured to load before other sub dir files
660        if (subDirLoadOrder != null) {
661            for (String pattern : subDirLoadOrder) {
662                List<File> matchedFiles = matchFiles(subDirFiles, pattern);
663                matchedFiles.removeAll(endFiles);
664                matchedFiles.removeAll(orderedFiles);
665
666                orderedFiles.addAll(matchedFiles);
667            }
668        }
669
670        // add remaining sub dir files
671        if (subDirFiles != null) {
672            for (File subDirFile : subDirFiles) {
673                if (!orderedFiles.contains(subDirFile) && !endFiles.contains(subDirFile)) {
674                    orderedFiles.add(subDirFile);
675                }
676            }
677        }
678
679        // now add the end files in reverse to the ordered list
680        File[] endFileArray = new File[endFiles.size()];
681        endFileArray = endFiles.toArray(endFileArray);
682
683        for (int i = endFileArray.length - 1; i >= 0; i--) {
684            orderedFiles.add(endFileArray[i]);
685        }
686
687        return orderedFiles;
688    }
689
690    /**
691     * Iterates through the list of files and returns those files whose names matches the given pattern
692     *
693     * @param filesToMatch list of files to match
694     * @param pattern pattern to match on
695     * @return list of files whose name that match the pattern
696     */
697    public static List<File> matchFiles(List<File> filesToMatch, String pattern) {
698        List<File> matchedFiles = new ArrayList<File>();
699
700        for (File file : filesToMatch) {
701            if (isMatch(file, pattern)) {
702                matchedFiles.add(file);
703            }
704        }
705
706        return matchedFiles;
707    }
708
709    /**
710     * Indicates whether the base name for the given file (name without path and the extension) matches
711     * the given pattern
712     *
713     * @param file file to match
714     * @param pattern pattern to match on
715     * @return true if the file name matches the pattern, false if not
716     */
717    public static boolean isMatch(File file, String pattern) {
718        boolean isMatch = false;
719
720        String fileBasename = FileUtils.basename(file.getName());
721        if (fileBasename.endsWith(".")) {
722            fileBasename = fileBasename.substring(0, fileBasename.length() - 1);
723        }
724
725        if (SelectorUtils.matchPath(pattern, fileBasename, false)) {
726            isMatch = true;
727        }
728
729        return isMatch;
730    }
731
732    /**
733     * Returns a list of files from the given list of files, that are contained within one of the given
734     * list of directories
735     *
736     * @param files list of files to filter
737     * @param directories list of directories to filter by
738     * @return list of files that are contained in the directories
739     */
740    public static List<File> getContainedFiles(List<File> files, List<File> directories) {
741        List<File> directoryFiles = new ArrayList<File>();
742
743        for (File directory : directories) {
744            for (File file : files) {
745                if (ThemeBuilderUtils.directoryContainsFile(directory, file.getName())) {
746                    directoryFiles.add(file);
747                }
748            }
749        }
750
751        return directoryFiles;
752    }
753
754    /**
755     * Adds the string to the end of the array of strings, or creates a new array containing the string
756     * if the array parameter is null
757     *
758     * @param array string array to add to
759     * @param stringToAdd string to add
760     * @return array containing all the original array elements plus the string
761     */
762    public static String[] addToArray(String[] array, String stringToAdd) {
763        String[] arrayToAdd = null;
764
765        if (stringToAdd != null) {
766            arrayToAdd = new String[1];
767            arrayToAdd[0] = stringToAdd;
768        }
769
770        return addToArray(array, arrayToAdd);
771    }
772
773    /**
774     * Adds the second array of strings to the end of the first array of strings, or creates a new array
775     * containing the second array elements if the first does not exist
776     *
777     * <p>
778     * Note: Can't use org.apache.commons.lang.ArrayUtils#addAll(java.lang.Object[], java.lang.Object[]) because it
779     * doesn't allow String arrays to be passed in. Latest version of ArrayUtils uses generics and does
780     * </p>
781     *
782     * @param array array to add to
783     * @param arrayToAdd array to add
784     * @return array containing all the strings from both arrays
785     */
786    public static String[] addToArray(String[] array, String[] arrayToAdd) {
787        if (array == null) {
788            return arrayToAdd;
789        } else if (arrayToAdd == null) {
790            return array;
791        }
792
793        int combinedArrayLength = array.length + arrayToAdd.length;
794
795        String[] combinedArray = new String[combinedArrayLength];
796
797        for (int i = 0; i < array.length; i++) {
798            combinedArray[i] = array[i];
799        }
800
801        for (int i = 0; i < arrayToAdd.length; i++) {
802            combinedArray[i + array.length] = arrayToAdd[i];
803        }
804
805        return combinedArray;
806    }
807
808    /**
809     * Builds a string formed with the name for each file in the list delimited by commas
810     *
811     * @param list list to join names for
812     * @return string containing all the file names
813     */
814    public static String joinFileList(List<File> list) {
815        String joinedString = null;
816
817        if (list != null) {
818            joinedString = "";
819
820            for (File file : list) {
821                if (!"".equals(joinedString)) {
822                    joinedString += ",";
823                }
824
825                joinedString += file.getName();
826            }
827        }
828
829        return joinedString;
830    }
831
832}