001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.cp; 018 019import static org.apache.juneau.common.utils.StringUtils.*; 020 021import java.net.*; 022import java.nio.file.*; 023 024import org.apache.juneau.common.utils.*; 025import org.apache.juneau.internal.*; 026 027/** 028 * Identifies a directory located either on the classpath or file system. 029 * 030 * Used to encapsulate basic resolution and retrieval of files regardless of where they are located. 031 * 032 * <h5 class='section'>See Also:</h5><ul> 033 * </ul> 034 */ 035public class LocalDir { 036 037 private final Class<?> clazz; 038 private final String clazzPath; 039 private final Path path; 040 private final int hashCode; 041 042 /** 043 * Constructor for classpath directory. 044 * 045 * @param clazz The class used to retrieve resources. 046 * @param clazzPath 047 * The subpath. Can be any of the following: 048 * <ul> 049 * <li><jk>null</jk> or an empty string - Package of the class. 050 * <li>Absolute path (starts with <js>'/'</js>) - Relative to root package. 051 * <li>Relative path (does not start with <js>'/'</js>) - Relative to class package. 052 * </ul> 053 */ 054 public LocalDir(Class<?> clazz, String clazzPath) { 055 this.clazz = Utils.assertArgNotNull("clazz", clazz); 056 this.clazzPath = "/".equals(clazzPath) ? "/" : Utils.nullIfEmpty3(trimTrailingSlashes(clazzPath)); 057 this.path = null; 058 this.hashCode = HashCode.of(clazz, clazzPath); 059 } 060 061 /** 062 * Constructor for file system directory. 063 * 064 * @param path Filesystem directory location. Must not be <jk>null</jk>. 065 */ 066 public LocalDir(Path path) { 067 this.clazz = null; 068 this.clazzPath = null; 069 this.path = Utils.assertArgNotNull("path", path); 070 this.hashCode = path.hashCode(); 071 } 072 073 /** 074 * Resolves the specified path. 075 * 076 * @param path 077 * The path to the file to resolve. 078 * <br>Must be a non-null relative path. 079 * <br>Does no cleanup of the path (e.g. checking for security holes or malformed values). 080 * @return The file if it exists, or <jk>null</jk> if it does not. 081 */ 082 public LocalFile resolve(String path) { 083 if (clazz != null) { 084 String p = clazzPath == null ? path : ("/".equals(clazzPath) ? "" : clazzPath) + '/' + path; 085 if (isClasspathFile(clazz.getResource(p))) 086 return new LocalFile(clazz, p); 087 } else { 088 Path p = this.path.resolve(path); 089 if (Files.isReadable(p) && ! Files.isDirectory(p)) 090 return new LocalFile(p); 091 } 092 return null; 093 } 094 095 /** 096 * Validates that the specified classpath resource exists and is a file. 097 * Note that the behavior of Class.getResource(path) is different when pointing to directories on the classpath. 098 * When packaged as a jar, calling Class.getResource(path) on a directory returns null. 099 * When unpackaged, calling Class.getResource(path) on a directory returns a URL starting with "file:". 100 * We perform a test to make the behavior the same regardless of whether we're packaged or not. 101 */ 102 private boolean isClasspathFile(URL url) { 103 try { 104 if (url == null) 105 return false; 106 URI uri = url.toURI(); 107 if (uri.toString().startsWith("file:")) 108 if (Files.isDirectory(Paths.get(uri))) 109 return false; 110 } catch (URISyntaxException e) { 111 e.printStackTrace(); // Untestable. 112 return false; 113 } 114 return true; 115 } 116 117 @Override /* Object */ 118 public boolean equals(Object o) { 119 return o instanceof LocalDir && Utils.eq(this, (LocalDir)o, (x,y)->Utils.eq(x.clazz, y.clazz) && Utils.eq(x.clazzPath, y.clazzPath) && Utils.eq(x.path, y.path)); 120 } 121 122 @Override /* Object */ 123 public int hashCode() { 124 return hashCode; 125 } 126 127 @Override /* Object */ 128 public String toString() { 129 if (clazz == null) 130 return path.toString(); 131 return clazz.getName() + ":" + clazzPath; 132 } 133}