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 }