001    /**
002     * Copyright 2010-2013 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     */
016    package org.kuali.common.util;
017    
018    import java.io.BufferedReader;
019    import java.io.BufferedWriter;
020    import java.io.File;
021    import java.io.FileWriter;
022    import java.io.IOException;
023    import java.io.InputStream;
024    import java.io.InputStreamReader;
025    import java.io.OutputStream;
026    import java.io.OutputStreamWriter;
027    import java.io.PrintStream;
028    import java.io.Reader;
029    import java.io.StringReader;
030    import java.io.Writer;
031    import java.net.MalformedURLException;
032    import java.net.URI;
033    import java.net.URISyntaxException;
034    import java.net.URL;
035    import java.util.ArrayList;
036    import java.util.Arrays;
037    import java.util.Collections;
038    import java.util.List;
039    import java.util.Properties;
040    
041    import org.apache.commons.io.FileUtils;
042    import org.apache.commons.io.IOUtils;
043    import org.apache.commons.lang3.StringUtils;
044    import org.slf4j.Logger;
045    import org.slf4j.LoggerFactory;
046    import org.springframework.core.io.DefaultResourceLoader;
047    import org.springframework.core.io.Resource;
048    import org.springframework.core.io.ResourceLoader;
049    import org.springframework.util.Assert;
050    
051    public class LocationUtils {
052    
053            private static final Logger logger = LoggerFactory.getLogger(LocationUtils.class);
054    
055            private static final String FILE_PREFIX = "file:";
056            private static final String BACK_SLASH = "\\";
057            private static final String FORWARD_SLASH = "/";
058            private static final String SLASH_DOT_SLASH = "/./";
059            private static final String DOT_DOT_SLASH = "../";
060            private static final String SLASH_DOT_DOT = "/..";
061            private static final String CLASSPATH = "classpath:";
062    
063            /**
064             * Open a <code>PrintStream</code> to the indicated file. Parent directories are created if necessary.
065             */
066            public static final PrintStream openPrintStream(File file) throws IOException {
067                    return new PrintStream(FileUtils.openOutputStream(file));
068            }
069    
070            /**
071             * Open a <code>Writer</code> to the indicated file. Parent directories are created if necessary.
072             */
073            public static final Writer openWriter(File file) throws IOException {
074                    touch(file);
075                    return new FileWriter(file);
076            }
077    
078            /**
079             * Open a <code>Writer</code> to the <code>location</code> (It must be a writable file on the local file system). Parent directories are created if necessary.
080             */
081            public static final Writer openWriter(String location) throws IOException {
082                    return openWriter(new File(location));
083            }
084    
085            public static Properties getLocationProperties(LocationPropertiesContext context) {
086    
087                    Assert.notNull(context, "context is null");
088    
089                    Properties properties = context.getProperties();
090                    String keySuffix = context.getKeySuffix();
091                    String locationPropertiesSuffix = context.getLocationPropertiesSuffix();
092                    String encoding = context.getEncoding();
093    
094                    Assert.notNull(properties, "properties is null");
095                    Assert.notNull(keySuffix, "keySuffix is null");
096                    Assert.notNull(locationPropertiesSuffix, "locationPropertiesSuffix is null");
097    
098                    List<String> keys = PropertyUtils.getEndsWithKeys(properties, keySuffix);
099    
100                    Properties locationProperties = new Properties();
101                    for (String key : keys) {
102                            String location = properties.getProperty(key);
103                            if (!exists(location)) {
104                                    continue;
105                            }
106                            String propertiesLocation = location + locationPropertiesSuffix;
107                            if (!exists(propertiesLocation)) {
108                                    continue;
109                            }
110                            Properties p = PropertyUtils.load(propertiesLocation, encoding);
111                            locationProperties.putAll(p);
112                    }
113                    logger.info("Located {} properties for {} location listings", locationProperties.size(), keys.size());
114                    return locationProperties;
115            }
116    
117            public static TextMetaData getTextMetaData(File file) {
118                    return getTextMetaData(getCanonicalPath(file));
119            }
120    
121            public static TextMetaData getTextMetaData(String location) {
122                    long lines = 0;
123                    long size = 0;
124                    BufferedReader in = null;
125                    try {
126                            in = getBufferedReader(location);
127                            String s = in.readLine();
128                            while (s != null) {
129                                    lines++;
130                                    size += s.length();
131                                    s = in.readLine();
132                            }
133                            return new TextMetaData(lines, size);
134                    } catch (IOException e) {
135                            throw new IllegalStateException(e);
136                    } finally {
137                            IOUtils.closeQuietly(in);
138                    }
139            }
140    
141            public static long getLineCount(File file) {
142                    return getLineCount(getCanonicalPath(file));
143            }
144    
145            public static long getLineCount(String location) {
146                    long count = 0;
147                    BufferedReader in = null;
148                    try {
149                            in = getBufferedReader(location);
150                            while (in.readLine() != null) {
151                                    count++;
152                            }
153                            return count;
154                    } catch (IOException e) {
155                            throw new IllegalStateException(e);
156                    } finally {
157                            IOUtils.closeQuietly(in);
158                    }
159            }
160    
161            public static final void copyLocationsToFiles(List<String> locations, List<File> files) {
162                    Assert.isTrue(locations.size() == files.size());
163                    for (int i = 0; i < locations.size(); i++) {
164                            String location = locations.get(i);
165                            File destination = files.get(i);
166                            copyLocationToFile(location, destination);
167                    }
168            }
169    
170            /**
171             * Return the text that appears after <code>classpath:</code>. Throws <code>IllegalArgumentException</code> if location does not start with <code>classpath:</code>
172             */
173            public static final String getClasspathFilename(String location) {
174                    return getClasspathFilenames(Arrays.asList(location)).get(0);
175            }
176    
177            /**
178             * Return the text that appears after <code>classpath:</code>. Throws <code>IllegalArgumentException</code> if any locations do not start with <code>classpath:</code>
179             */
180            public static final List<String> getClasspathFilenames(List<String> locations) {
181                    List<String> classpathFilenames = new ArrayList<String>();
182                    for (String location : locations) {
183                            if (!isClasspathLocation(location)) {
184                                    throw new IllegalArgumentException(location + " must start with " + CLASSPATH);
185                            } else {
186                                    classpathFilenames.add(StringUtils.substring(location, CLASSPATH.length()));
187                            }
188                    }
189                    return classpathFilenames;
190            }
191    
192            /**
193             * Return <code>true</code> if location starts with <code>classpath:</code>
194             */
195            public static final boolean isClasspathLocation(String location) {
196                    return StringUtils.startsWith(location, CLASSPATH);
197            }
198    
199            public static final List<String> getNormalizedPathFragments(String absolutePath, boolean directory) {
200                    String normalized = getNormalizedAbsolutePath(absolutePath);
201                    String[] tokens = StringUtils.split(normalized, FORWARD_SLASH);
202                    List<String> fragments = new ArrayList<String>();
203                    StringBuilder sb = new StringBuilder();
204                    sb.append(FORWARD_SLASH);
205                    int length = directory ? tokens.length : tokens.length - 1;
206                    for (int i = 0; i < length; i++) {
207                            if (i != 0) {
208                                    sb.append(FORWARD_SLASH);
209                            }
210                            sb.append(tokens[i]);
211                            fragments.add(sb.toString());
212                    }
213                    return fragments;
214            }
215    
216            public static final List<String> getCanonicalPaths(List<File> files) {
217                    List<String> paths = new ArrayList<String>();
218                    for (File file : files) {
219                            String path = getCanonicalPath(file);
220                            paths.add(path);
221                    }
222                    return paths;
223            }
224    
225            public static final List<String> getLocations(String location, LocationType type, String encoding) {
226                    switch (type) {
227                    case LOCATION:
228                            return Collections.singletonList(location);
229                    case LOCATIONLIST:
230                            return getLocations(location, encoding);
231                    default:
232                            throw new IllegalArgumentException("Location type '" + type + "' is unknown");
233                    }
234            }
235    
236            public static final List<String> getLocations(String location, LocationType type) {
237                    return getLocations(location, type, null);
238            }
239    
240            public static final List<String> getLocations(String locationListing) {
241                    return getLocations(Collections.singletonList(locationListing), null);
242            }
243    
244            public static final List<String> getLocations(String locationListing, String encoding) {
245                    return getLocations(Collections.singletonList(locationListing), encoding);
246            }
247    
248            public static final List<String> getLocations(List<String> locationListings) {
249                    return getLocations(locationListings, null);
250            }
251    
252            public static final void copyLocationToFile(String location, File destination) {
253                    Assert.notNull(location);
254                    Assert.notNull(destination);
255                    logger.debug("Copying [{}]->[{}]", location, destination);
256                    InputStream in = null;
257                    try {
258                            in = getInputStream(location);
259                            FileUtils.copyInputStreamToFile(in, destination);
260                    } catch (IOException e) {
261                            throw new IllegalStateException(e);
262                    } finally {
263                            IOUtils.closeQuietly(in);
264                    }
265            }
266    
267            public static final List<File> getFiles(File dir, List<String> filenames) {
268                    List<File> files = new ArrayList<File>();
269                    for (String filename : filenames) {
270                            File file = new File(dir, filename);
271                            files.add(file);
272                    }
273                    return files;
274            }
275    
276            public static final List<String> getFilenames(List<String> locations) {
277                    Assert.notNull(locations);
278                    List<String> filenames = new ArrayList<String>();
279                    for (String location : locations) {
280                            filenames.add(getFilename(location));
281                    }
282                    return filenames;
283            }
284    
285            public static final List<String> getLocations(List<String> locationListings, String encoding) {
286                    List<String> locations = new ArrayList<String>();
287                    for (String locationListing : locationListings) {
288                            List<String> lines = readLines(locationListing, encoding);
289                            locations.addAll(lines);
290                    }
291                    return locations;
292            }
293    
294            public static final String getCanonicalURLString(File file) {
295                    if (file == null) {
296                            return null;
297                    }
298                    String path = getCanonicalPath(file);
299                    File canonical = new File(path);
300                    return getURLString(canonical);
301            }
302    
303            public static final void validateNormalizedPath(String originalPath, String normalizedPath) {
304                    if (CollectionUtils.containsAny(normalizedPath, Arrays.asList(SLASH_DOT_DOT, SLASH_DOT_SLASH, DOT_DOT_SLASH))) {
305                            throw new IllegalArgumentException("[" + originalPath + "] could not be normalized. Normalized path [" + normalizedPath + "]");
306                    }
307            }
308    
309            /**
310             * Resolve and remove <code>..</code> and <code>.</code> from <code>absolutePath</code> after converting any back slashes to forward slashes
311             */
312            public static final String getNormalizedAbsolutePath(String absolutePath) {
313                    if (absolutePath == null) {
314                            return null;
315                    }
316                    String replaced = StringUtils.replace(absolutePath, BACK_SLASH, FORWARD_SLASH);
317                    boolean absolute = StringUtils.startsWith(replaced, FORWARD_SLASH);
318                    if (!absolute) {
319                            throw new IllegalArgumentException("[" + absolutePath + "] is not an absolute path.");
320                    }
321                    String prefixed = FILE_PREFIX + replaced;
322                    try {
323                            URI rawURI = new URI(prefixed);
324                            URI normalizedURI = rawURI.normalize();
325                            URL normalizedURL = normalizedURI.toURL();
326                            String externalForm = normalizedURL.toExternalForm();
327                            String trimmed = StringUtils.substring(externalForm, FILE_PREFIX.length());
328                            validateNormalizedPath(absolutePath, trimmed);
329                            return trimmed;
330                    } catch (MalformedURLException e) {
331                            throw new IllegalArgumentException(e);
332                    } catch (URISyntaxException e) {
333                            throw new IllegalArgumentException(e);
334                    }
335            }
336    
337            public static final String getURLString(File file) {
338                    if (file == null) {
339                            return null;
340                    }
341                    try {
342                            URI uri = file.toURI();
343                            URL url = uri.toURL();
344                            return url.toExternalForm();
345                    } catch (MalformedURLException e) {
346                            throw new IllegalArgumentException(e);
347                    }
348            }
349    
350            public static final void forceMkdir(File file) {
351                    try {
352                            FileUtils.forceMkdir(file);
353                    } catch (IOException e) {
354                            throw new IllegalArgumentException("Unexpected IO error", e);
355                    }
356            }
357    
358            public static final void touch(File file) {
359                    try {
360                            FileUtils.touch(file);
361                    } catch (IOException e) {
362                            throw new IllegalArgumentException("Unexpected IO error", e);
363                    }
364            }
365    
366            public static final String getCanonicalPath(File file) {
367                    try {
368                            return file.getCanonicalPath();
369                    } catch (IOException e) {
370                            throw new IllegalArgumentException("Unexpected IO error", e);
371                    }
372            }
373    
374            /**
375             * Null safe method to unconditionally attempt to delete <code>filename</code> without throwing an exception. If <code>filename</code> is a directory, delete it and all
376             * sub-directories.
377             */
378            public static final boolean deleteFileQuietly(String filename) {
379                    File file = getFileQuietly(filename);
380                    return FileUtils.deleteQuietly(file);
381            }
382    
383            /**
384             * Null safe method for getting a <code>File</code> handle from <code>filename</code>. If <code>filename</code> is null, null is returned.
385             */
386            public static final File getFileQuietly(String filename) {
387                    if (filename == null) {
388                            return null;
389                    } else {
390                            return new File(filename);
391                    }
392            }
393    
394            /**
395             * Get the contents of <code>file</code> as a <code>String</code> using the platform's default character encoding.
396             */
397            public static final String toString(File file) {
398                    return toString(file, null);
399            }
400    
401            /**
402             * Get the contents of <code>file</code> as a <code>String</code> using the specified character encoding.
403             */
404            public static final String toString(File file, String encoding) {
405                    return toString(getCanonicalPath(file), encoding);
406            }
407    
408            /**
409             * Get the contents of <code>location</code> as a <code>String</code> using the platform's default character encoding.
410             */
411            public static final String toString(String location) {
412                    return toString(location, null);
413            }
414    
415            /**
416             * Get the contents of <code>location</code> as a <code>String</code> using the specified character encoding.
417             */
418            public static final String toString(String location, String encoding) {
419                    InputStream in = null;
420                    try {
421                            in = getInputStream(location);
422                            if (encoding == null) {
423                                    return IOUtils.toString(in);
424                            } else {
425                                    return IOUtils.toString(in, encoding);
426                            }
427                    } catch (IOException e) {
428                            throw new IllegalStateException("Unexpected IO error", e);
429                    } finally {
430                            IOUtils.closeQuietly(in);
431                    }
432            }
433    
434            /**
435             * Get the contents of <code>s</code> as a list of <code>String's</code> one entry per line
436             */
437            public static final List<String> readLinesFromString(String s) {
438                    Reader reader = getBufferedReaderFromString(s);
439                    return readLinesAndClose(reader);
440            }
441    
442            public static final List<String> readLinesAndClose(InputStream in) {
443                    return readLinesAndClose(in, null);
444            }
445    
446            public static final List<String> readLinesAndClose(InputStream in, String encoding) {
447                    Reader reader = null;
448                    try {
449                            reader = getBufferedReader(in, encoding);
450                            return readLinesAndClose(reader);
451                    } catch (IOException e) {
452                            throw new IllegalStateException("Unexpected IO error", e);
453                    } finally {
454                            IOUtils.closeQuietly(reader);
455                    }
456            }
457    
458            public static final List<String> readLinesAndClose(Reader reader) {
459                    try {
460                            return IOUtils.readLines(reader);
461                    } catch (IOException e) {
462                            throw new IllegalStateException("Unexpected IO error", e);
463                    } finally {
464                            IOUtils.closeQuietly(reader);
465                    }
466            }
467    
468            /**
469             * Get the contents of <code>file</code> as a list of <code>String's</code> one entry per line using the platform default encoding
470             */
471            public static final List<String> readLines(File file) {
472                    return readLines(getCanonicalPath(file));
473            }
474    
475            /**
476             * Get the contents of <code>location</code> as a list of <code>String's</code> one entry per line using the platform default encoding
477             */
478            public static final List<String> readLines(String location) {
479                    return readLines(location, null);
480            }
481    
482            /**
483             * Get the contents of <code>location</code> as a list of <code>String's</code> one entry per line using the encoding indicated.
484             */
485            public static final List<String> readLines(String location, String encoding) {
486                    Reader reader = null;
487                    try {
488                            reader = getBufferedReader(location, encoding);
489                            return readLinesAndClose(reader);
490                    } catch (IOException e) {
491                            throw new IllegalStateException("Unexpected IO error", e);
492                    } finally {
493                            IOUtils.closeQuietly(reader);
494                    }
495            }
496    
497            /**
498             * Return a <code>BufferedReader</code> for the location indicated using the platform default encoding.
499             */
500            public static final BufferedReader getBufferedReader(String location) throws IOException {
501                    return getBufferedReader(location, null);
502            }
503    
504            /**
505             * Return a <code>BufferedReader</code> for the location indicated using the encoding indicated.
506             */
507            public static final BufferedReader getBufferedReader(String location, String encoding) throws IOException {
508                    try {
509                            InputStream in = getInputStream(location);
510                            return getBufferedReader(in, encoding);
511                    } catch (IOException e) {
512                            throw new IOException("Unexpected IO error", e);
513                    }
514            }
515    
516            /**
517             * Return a <code>BufferedReader</code> that reads from <code>s</code>
518             */
519            public static final BufferedReader getBufferedReaderFromString(String s) {
520                    return new BufferedReader(new StringReader(s));
521            }
522    
523            /**
524             * Return a <code>Writer</code> that writes to <code>out</code> using the indicated encoding. <code>null</code> means use the platform's default encoding.
525             */
526            public static final Writer getWriter(OutputStream out, String encoding) throws IOException {
527                    if (encoding == null) {
528                            return new BufferedWriter(new OutputStreamWriter(out));
529                    } else {
530                            return new BufferedWriter(new OutputStreamWriter(out, encoding));
531                    }
532            }
533    
534            /**
535             * Return a <code>BufferedReader</code> that reads from <code>file</code> using the indicated encoding. <code>null</code> means use the platform's default encoding.
536             */
537            public static final BufferedReader getBufferedReader(File file, String encoding) throws IOException {
538                    return getBufferedReader(FileUtils.openInputStream(file), encoding);
539            }
540    
541            /**
542             * Return a <code>BufferedReader</code> that reads from <code>in</code> using the indicated encoding. <code>null</code> means use the platform's default encoding.
543             */
544            public static final BufferedReader getBufferedReader(InputStream in, String encoding) throws IOException {
545                    if (encoding == null) {
546                            return new BufferedReader(new InputStreamReader(in));
547                    } else {
548                            return new BufferedReader(new InputStreamReader(in, encoding));
549                    }
550            }
551    
552            /**
553             * Null safe method for determining if <code>location</code> is an existing file.
554             */
555            public static final boolean isExistingFile(String location) {
556                    if (location == null) {
557                            return false;
558                    }
559                    File file = new File(location);
560                    return file.exists();
561            }
562    
563            /**
564             * Null safe method for determining if <code>location</code> exists.
565             */
566            public static final boolean exists(File file) {
567                    if (file == null) {
568                            return false;
569                    }
570                    String location = getCanonicalPath(file);
571                    if (isExistingFile(location)) {
572                            return true;
573                    } else {
574                            Resource resource = getResource(location);
575                            return resource.exists();
576                    }
577            }
578    
579            /**
580             * Null safe method for determining if <code>location</code> exists.
581             */
582            public static final boolean exists(String location) {
583                    if (location == null) {
584                            return false;
585                    }
586                    if (isExistingFile(location)) {
587                            return true;
588                    } else {
589                            Resource resource = getResource(location);
590                            return resource.exists();
591                    }
592            }
593    
594            /**
595             * Open an <code>InputStream</code> to <code>location</code>. If <code>location</code> is the path to an existing <code>File</code> on the local file system, a
596             * <code>FileInputStream</code> is returned. Otherwise Spring's resource loading framework is used to open an <code>InputStream</code> to <code>location</code>.
597             */
598            public static final InputStream getInputStream(String location) throws IOException {
599                    if (isExistingFile(location)) {
600                            return FileUtils.openInputStream(new File(location));
601                    }
602                    Resource resource = getResource(location);
603                    return resource.getInputStream();
604            }
605    
606            public static final Resource getResource(String location) {
607                    if (location == null) {
608                            return null;
609                    }
610                    ResourceLoader loader = new DefaultResourceLoader();
611                    return loader.getResource(location);
612            }
613    
614            public static final String getFilename(String location) {
615                    if (location == null) {
616                            return null;
617                    }
618                    if (isExistingFile(location)) {
619                            return getFileQuietly(location).getName();
620                    } else {
621                            Resource resource = getResource(location);
622                            return resource.getFilename();
623                    }
624            }
625    
626            public static final List<String> getAbsolutePaths(List<File> files) {
627                    List<String> results = new ArrayList<String>(files.size());
628    
629                    for (File f : files) {
630                            results.add(f.getAbsolutePath());
631                    }
632    
633                    return results;
634            }
635    
636            public static final ComparisonResults getLocationListComparison(List<String> newLocations, List<String> originalLocations) {
637                    ComparisonResults result = new ComparisonResults();
638    
639                    result.setAdded(new ArrayList<String>());
640                    result.setSame(new ArrayList<String>());
641                    result.setDeleted(new ArrayList<String>());
642    
643                    for (String newLocation : newLocations) {
644                            if (originalLocations.contains(newLocation)) {
645                                    // if a location is in both lists, add it to the "same" list
646                                    result.getSame().add(newLocation);
647                            } else {
648                                    // if a location is only in the new list, add it to the "added" list
649                                    result.getAdded().add(newLocation);
650                            }
651                    }
652    
653                    // the "deleted" list will contain all locations from the original list that are NOT in the new list
654                    result.getDeleted().addAll(originalLocations);
655                    result.getDeleted().removeAll(newLocations);
656    
657                    return result;
658            }
659    
660    }