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