View Javadoc

1   /**
2    * Copyright 2010-2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.common.util;
17  
18  import java.io.BufferedReader;
19  import java.io.BufferedWriter;
20  import java.io.File;
21  import java.io.FileOutputStream;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.InputStreamReader;
25  import java.io.OutputStream;
26  import java.io.OutputStreamWriter;
27  import java.io.PrintStream;
28  import java.io.Reader;
29  import java.io.StringReader;
30  import java.io.Writer;
31  import java.net.MalformedURLException;
32  import java.net.URI;
33  import java.net.URISyntaxException;
34  import java.net.URL;
35  import java.util.ArrayList;
36  import java.util.Arrays;
37  import java.util.Collections;
38  import java.util.List;
39  
40  import org.apache.commons.io.FileUtils;
41  import org.apache.commons.io.IOUtils;
42  import org.apache.commons.lang3.StringUtils;
43  import org.slf4j.Logger;
44  import org.slf4j.LoggerFactory;
45  import org.springframework.core.io.DefaultResourceLoader;
46  import org.springframework.core.io.Resource;
47  import org.springframework.core.io.ResourceLoader;
48  
49  public class LocationUtils {
50  
51  	private static final Logger logger = LoggerFactory.getLogger(LocationUtils.class);
52  
53  	private static final String FILE_PREFIX = "file:";
54  	private static final String BACK_SLASH = "\\";
55  	private static final String FORWARD_SLASH = "/";
56  	private static final String SLASH_DOT_SLASH = "/./";
57  	private static final String DOT_DOT_SLASH = "../";
58  	private static final String SLASH_DOT_DOT = "/..";
59  	private static final String CLASSPATH = "classpath:";
60  
61  	/**
62  	 * Opens a {@link FileOutputStream} for the specified file, checking and creating the parent directory if it does not exist.
63  	 * <p>
64  	 * At the end of the method either the stream will be successfully opened, or an exception will have been thrown.
65  	 * <p>
66  	 * The parent directory will be created if it does not exist. The file will be created if it does not exist. An exception is thrown if
67  	 * the file object exists but is a directory. An exception is thrown if the file exists but cannot be written to. An exception is thrown
68  	 * if the parent directory cannot be created.
69  	 *
70  	 * @param file
71  	 *            the file to open for output, must not be {@code null}
72  	 * @param append
73  	 *            if {@code true}, then bytes will be added to the end of the file rather than overwriting
74  	 * @return a new {@link FileOutputStream} for the specified file
75  	 * @throws IOException
76  	 *             if the file object is a directory
77  	 * @throws IOException
78  	 *             if the file cannot be written to
79  	 * @throws IOException
80  	 *             if a parent directory needs creating but that fails
81  	 */
82  	public static final PrintStream openPrintStream(File file) throws IOException {
83  		return new PrintStream(FileUtils.openOutputStream(file));
84  	}
85  
86  	public static final void copyLocationsToFiles(List<String> locations, List<File> files) {
87  		Assert.isTrue(locations.size() == files.size());
88  		for (int i = 0; i < locations.size(); i++) {
89  			String location = locations.get(i);
90  			File destination = files.get(i);
91  			LocationUtils.copyLocationToFile(location, destination);
92  		}
93  	}
94  
95  	/**
96  	 * Return the text that appears after <code>classpath:</code>. Throws <code>IllegalArgumentException</code> if location does not start
97  	 * with <code>classpath:</code>
98  	 */
99  	public static final String getClasspathFilename(String location) {
100 		return getClasspathFilenames(Arrays.asList(location)).get(0);
101 	}
102 
103 	/**
104 	 * Return the text that appears after <code>classpath:</code>. Throws <code>IllegalArgumentException</code> if any locations do not
105 	 * start with <code>classpath:</code>
106 	 */
107 	public static final List<String> getClasspathFilenames(List<String> locations) {
108 		List<String> classpathFilenames = new ArrayList<String>();
109 		for (String location : locations) {
110 			if (!isClasspathLocation(location)) {
111 				throw new IllegalArgumentException(location + " must start with " + CLASSPATH);
112 			} else {
113 				classpathFilenames.add(StringUtils.substring(location, CLASSPATH.length()));
114 			}
115 		}
116 		return classpathFilenames;
117 	}
118 
119 	/**
120 	 * Return <code>true</code> if location starts with <code>classpath:</code>
121 	 */
122 	public static final boolean isClasspathLocation(String location) {
123 		return StringUtils.startsWith(location, CLASSPATH);
124 	}
125 
126 	public static final List<String> getNormalizedPathFragments(String absolutePath, boolean directory) {
127 		String normalized = getNormalizedAbsolutePath(absolutePath);
128 		String[] tokens = StringUtils.split(normalized, FORWARD_SLASH);
129 		List<String> fragments = new ArrayList<String>();
130 		StringBuilder sb = new StringBuilder();
131 		sb.append(FORWARD_SLASH);
132 		int length = directory ? tokens.length : tokens.length - 1;
133 		for (int i = 0; i < length; i++) {
134 			if (i != 0) {
135 				sb.append(FORWARD_SLASH);
136 			}
137 			sb.append(tokens[i]);
138 			fragments.add(sb.toString());
139 		}
140 		return fragments;
141 	}
142 
143 	public static final List<String> getLocations(String location, LocationType type, String encoding) {
144 		switch (type) {
145 		case LOCATION:
146 			return Collections.singletonList(location);
147 		case LOCATIONLIST:
148 			return getLocations(location, encoding);
149 		default:
150 			throw new IllegalArgumentException("Location type '" + type + "' is unknown");
151 		}
152 	}
153 
154 	public static final List<String> getLocations(String location, LocationType type) {
155 		return getLocations(location, type, null);
156 	}
157 
158 	public static final List<String> getLocations(String locationListing) {
159 		return getLocations(Collections.singletonList(locationListing), null);
160 	}
161 
162 	public static final List<String> getLocations(String locationListing, String encoding) {
163 		return getLocations(Collections.singletonList(locationListing), encoding);
164 	}
165 
166 	public static final List<String> getLocations(List<String> locationListings) {
167 		return getLocations(locationListings, null);
168 	}
169 
170 	public static final void copyLocationToFile(String location, File destination) {
171 		Assert.notNull(location);
172 		Assert.notNull(destination);
173 		logger.debug("Copying [{}]->[{}]", location, destination);
174 		InputStream in = null;
175 		try {
176 			in = getInputStream(location);
177 			FileUtils.copyInputStreamToFile(in, destination);
178 		} catch (IOException e) {
179 			throw new IllegalStateException(e);
180 		} finally {
181 			IOUtils.closeQuietly(in);
182 		}
183 	}
184 
185 	public static final List<File> getFiles(File dir, List<String> filenames) {
186 		List<File> files = new ArrayList<File>();
187 		for (String filename : filenames) {
188 			File file = new File(dir, filename);
189 			files.add(file);
190 		}
191 		return files;
192 	}
193 
194 	public static final List<String> getFilenames(List<String> locations) {
195 		Assert.notNull(locations);
196 		List<String> filenames = new ArrayList<String>();
197 		for (String location : locations) {
198 			filenames.add(getFilename(location));
199 		}
200 		return filenames;
201 	}
202 
203 	public static final List<String> getLocations(List<String> locationListings, String encoding) {
204 		List<String> locations = new ArrayList<String>();
205 		for (String locationListing : locationListings) {
206 			List<String> lines = readLines(locationListing, encoding);
207 			locations.addAll(lines);
208 		}
209 		return locations;
210 	}
211 
212 	public static final String getCanonicalURLString(File file) {
213 		if (file == null) {
214 			return null;
215 		}
216 		String path = getCanonicalPath(file);
217 		File canonical = new File(path);
218 		return getURLString(canonical);
219 	}
220 
221 	public static final void validateNormalizedPath(String originalPath, String normalizedPath) {
222 		if (CollectionUtils.containsAny(normalizedPath, Arrays.asList(SLASH_DOT_DOT, SLASH_DOT_SLASH, DOT_DOT_SLASH))) {
223 			throw new IllegalArgumentException("[" + originalPath + "] could not be normalized. Normalized path [" + normalizedPath + "]");
224 		}
225 	}
226 
227 	/**
228 	 * Resolve and remove <code>..</code> and <code>.</code> from <code>absolutePath</code> after converting any back slashes to forward
229 	 * slashes
230 	 */
231 	public static final String getNormalizedAbsolutePath(String absolutePath) {
232 		if (absolutePath == null) {
233 			return null;
234 		}
235 		String replaced = StringUtils.replace(absolutePath, BACK_SLASH, FORWARD_SLASH);
236 		boolean absolute = StringUtils.startsWith(replaced, FORWARD_SLASH);
237 		if (!absolute) {
238 			throw new IllegalArgumentException("[" + absolutePath + "] is not an absolute path.");
239 		}
240 		String prefixed = FILE_PREFIX + replaced;
241 		try {
242 			URI rawURI = new URI(prefixed);
243 			URI normalizedURI = rawURI.normalize();
244 			URL normalizedURL = normalizedURI.toURL();
245 			String externalForm = normalizedURL.toExternalForm();
246 			String trimmed = StringUtils.substring(externalForm, FILE_PREFIX.length());
247 			validateNormalizedPath(absolutePath, trimmed);
248 			return trimmed;
249 		} catch (MalformedURLException e) {
250 			throw new IllegalArgumentException(e);
251 		} catch (URISyntaxException e) {
252 			throw new IllegalArgumentException(e);
253 		}
254 	}
255 
256 	public static final String getURLString(File file) {
257 		if (file == null) {
258 			return null;
259 		}
260 		try {
261 			URI uri = file.toURI();
262 			URL url = uri.toURL();
263 			return url.toExternalForm();
264 		} catch (MalformedURLException e) {
265 			throw new IllegalArgumentException(e);
266 		}
267 	}
268 
269 	public static final void forceMkdir(File file) {
270 		try {
271 			FileUtils.forceMkdir(file);
272 		} catch (IOException e) {
273 			throw new IllegalArgumentException("Unexpected IO error", e);
274 		}
275 	}
276 
277 	public static final void touch(File file) {
278 		try {
279 			FileUtils.touch(file);
280 		} catch (IOException e) {
281 			throw new IllegalArgumentException("Unexpected IO error", e);
282 		}
283 	}
284 
285 	public static final String getCanonicalPath(File file) {
286 		try {
287 			return file.getCanonicalPath();
288 		} catch (IOException e) {
289 			throw new IllegalArgumentException("Unexpected IO error", e);
290 		}
291 	}
292 
293 	/**
294 	 * Null safe method to unconditionally attempt to delete <code>filename</code> without throwing an exception. If <code>filename</code>
295 	 * is a directory, delete it and all sub-directories.
296 	 */
297 	public static final boolean deleteFileQuietly(String filename) {
298 		File file = getFileQuietly(filename);
299 		return FileUtils.deleteQuietly(file);
300 	}
301 
302 	/**
303 	 * Null safe method for getting a <code>File</code> handle from <code>filename</code>. If <code>filename</code> is null, null is
304 	 * returned.
305 	 */
306 	public static final File getFileQuietly(String filename) {
307 		if (filename == null) {
308 			return null;
309 		} else {
310 			return new File(filename);
311 		}
312 	}
313 
314 	/**
315 	 * Get the contents of <code>location</code> as a <code>String</code> using the platform's default character encoding.
316 	 */
317 	public static final String toString(String location) {
318 		return toString(location, null);
319 	}
320 
321 	/**
322 	 * Get the contents of <code>location</code> as a <code>String</code> using the specified character encoding.
323 	 */
324 	public static final String toString(String location, String encoding) {
325 		InputStream in = null;
326 		try {
327 			in = getInputStream(location);
328 			if (encoding == null) {
329 				return IOUtils.toString(in);
330 			} else {
331 				return IOUtils.toString(in, encoding);
332 			}
333 		} catch (IOException e) {
334 			throw new IllegalStateException("Unexpected IO error", e);
335 		} finally {
336 			IOUtils.closeQuietly(in);
337 		}
338 	}
339 
340 	/**
341 	 * Get the contents of <code>s</code> as a list of <code>String's</code> one entry per line
342 	 */
343 	public static final List<String> readLinesFromString(String s) {
344 		Reader reader = getBufferedReaderFromString(s);
345 		return readLinesAndClose(reader);
346 	}
347 
348 	public static final List<String> readLinesAndClose(InputStream in) {
349 		return readLinesAndClose(in, null);
350 	}
351 
352 	public static final List<String> readLinesAndClose(InputStream in, String encoding) {
353 		Reader reader = null;
354 		try {
355 			reader = getBufferedReader(in, encoding);
356 			return readLinesAndClose(reader);
357 		} catch (IOException e) {
358 			throw new IllegalStateException("Unexpected IO error", e);
359 		} finally {
360 			IOUtils.closeQuietly(reader);
361 		}
362 	}
363 
364 	public static final List<String> readLinesAndClose(Reader reader) {
365 		try {
366 			return IOUtils.readLines(reader);
367 		} catch (IOException e) {
368 			throw new IllegalStateException("Unexpected IO error", e);
369 		} finally {
370 			IOUtils.closeQuietly(reader);
371 		}
372 	}
373 
374 	/**
375 	 * Get the contents of <code>file</code> as a list of <code>String's</code> one entry per line using the platform default encoding
376 	 */
377 	public static final List<String> readLines(File file) {
378 		return readLines(getCanonicalPath(file));
379 	}
380 
381 	/**
382 	 * Get the contents of <code>location</code> as a list of <code>String's</code> one entry per line using the platform default encoding
383 	 */
384 	public static final List<String> readLines(String location) {
385 		return readLines(location, null);
386 	}
387 
388 	/**
389 	 * Get the contents of <code>location</code> as a list of <code>String's</code> one entry per line using the encoding indicated.
390 	 */
391 	public static final List<String> readLines(String location, String encoding) {
392 		Reader reader = null;
393 		try {
394 			reader = getBufferedReader(location, encoding);
395 			return readLinesAndClose(reader);
396 		} catch (IOException e) {
397 			throw new IllegalStateException("Unexpected IO error", e);
398 		} finally {
399 			IOUtils.closeQuietly(reader);
400 		}
401 	}
402 
403 	/**
404 	 * Return a <code>BufferedReader</code> for the location indicated using the platform default encoding.
405 	 */
406 	public static final BufferedReader getBufferedReader(String location) throws IOException {
407 		return getBufferedReader(location, null);
408 	}
409 
410 	/**
411 	 * Return a <code>BufferedReader</code> for the location indicated using the encoding indicated.
412 	 */
413 	public static final BufferedReader getBufferedReader(String location, String encoding) throws IOException {
414 		try {
415 			InputStream in = getInputStream(location);
416 			return getBufferedReader(in, encoding);
417 		} catch (IOException e) {
418 			throw new IOException("Unexpected IO error", e);
419 		}
420 	}
421 
422 	/**
423 	 * Return a <code>BufferedReader</code> that reads from <code>s</code>
424 	 */
425 	public static final BufferedReader getBufferedReaderFromString(String s) {
426 		return new BufferedReader(new StringReader(s));
427 	}
428 
429 	/**
430 	 * Return a <code>Writer</code> that writes to <code>out</code> using the indicated encoding. <code>null</code> means use the platform's
431 	 * default encoding.
432 	 */
433 	public static final Writer getWriter(OutputStream out, String encoding) throws IOException {
434 		if (encoding == null) {
435 			return new BufferedWriter(new OutputStreamWriter(out));
436 		} else {
437 			return new BufferedWriter(new OutputStreamWriter(out, encoding));
438 		}
439 	}
440 
441 	/**
442 	 * Return a <code>BufferedReader</code> that reads from <code>file</code> using the indicated encoding. <code>null</code> means use the
443 	 * platform's default encoding.
444 	 */
445 	public static final BufferedReader getBufferedReader(File file, String encoding) throws IOException {
446 		return getBufferedReader(FileUtils.openInputStream(file), encoding);
447 	}
448 
449 	/**
450 	 * Return a <code>BufferedReader</code> that reads from <code>in</code> using the indicated encoding. <code>null</code> means use the
451 	 * platform's default encoding.
452 	 */
453 	public static final BufferedReader getBufferedReader(InputStream in, String encoding) throws IOException {
454 		if (encoding == null) {
455 			return new BufferedReader(new InputStreamReader(in));
456 		} else {
457 			return new BufferedReader(new InputStreamReader(in, encoding));
458 		}
459 	}
460 
461 	/**
462 	 * Null safe method for determining if <code>location</code> is an existing file.
463 	 */
464 	public static final boolean isExistingFile(String location) {
465 		if (location == null) {
466 			return false;
467 		}
468 		File file = new File(location);
469 		return file.exists();
470 	}
471 
472 	/**
473 	 * Null safe method for determining if <code>location</code> exists.
474 	 */
475 	public static final boolean exists(File file) {
476 		if (file == null) {
477 			return false;
478 		}
479 		String location = getCanonicalPath(file);
480 		if (isExistingFile(location)) {
481 			return true;
482 		} else {
483 			Resource resource = getResource(location);
484 			return resource.exists();
485 		}
486 	}
487 
488 	/**
489 	 * Null safe method for determining if <code>location</code> exists.
490 	 */
491 	public static final boolean exists(String location) {
492 		if (location == null) {
493 			return false;
494 		}
495 		if (isExistingFile(location)) {
496 			return true;
497 		} else {
498 			Resource resource = getResource(location);
499 			return resource.exists();
500 		}
501 	}
502 
503 	/**
504 	 * Open an <code>InputStream</code> to <code>location</code>. If <code>location</code> is the path to an existing <code>File</code> on
505 	 * the local file system, a <code>FileInputStream</code> is returned. Otherwise Spring's resource loading framework is used to open an
506 	 * <code>InputStream</code> to <code>location</code>.
507 	 */
508 	public static final InputStream getInputStream(String location) throws IOException {
509 		if (isExistingFile(location)) {
510 			return FileUtils.openInputStream(new File(location));
511 		}
512 		Resource resource = getResource(location);
513 		return resource.getInputStream();
514 	}
515 
516 	public static final Resource getResource(String location) {
517 		if (location == null) {
518 			return null;
519 		}
520 		ResourceLoader loader = new DefaultResourceLoader();
521 		return loader.getResource(location);
522 	}
523 
524 	public static final String getFilename(String location) {
525 		if (location == null) {
526 			return null;
527 		}
528 		if (isExistingFile(location)) {
529 			return getFileQuietly(location).getName();
530 		} else {
531 			Resource resource = getResource(location);
532 			return resource.getFilename();
533 		}
534 	}
535 
536 }