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