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 }