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}