001/*-------------------------------------------------------------------------+ 002| | 003| Copyright 2005-2011 The ConQAT Project | 004| | 005| Licensed under the Apache License, Version 2.0 (the "License"); | 006| you may not use this file except in compliance with the License. | 007| 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.conqat.lib.commons.resources; 018 019import java.io.File; 020import java.io.FileNotFoundException; 021import java.io.IOException; 022import java.net.MalformedURLException; 023import java.net.URISyntaxException; 024import java.net.URL; 025import java.util.ArrayList; 026import java.util.HashSet; 027import java.util.List; 028import java.util.Set; 029 030import org.conqat.lib.commons.collections.CollectionUtils; 031import org.conqat.lib.commons.filesystem.FileSystemUtils; 032 033/** 034 * Helper class for listing resources in a given path. 035 */ 036public class ResourceUtils { 037 038 /** Extension of class files that should not be considered as a resource. */ 039 private static final String CLASS_FILE_EXTENSION = ".class"; 040 041 /** 042 * Lists all resources in the given path (non-recursive). Only resources and no 043 * resource folders will be returned. 044 * 045 * @param path 046 * The path to list resources from. An empty string means the root of 047 * the contextClass' package. 048 */ 049 public static List<Resource> listResources(Class<?> contextClass, String path) { 050 try { 051 return listResources(contextClass, path, false); 052 } catch (IOException e) { 053 throw ResourceException.newListFailed(path, contextClass, e); 054 } 055 } 056 057 /** 058 * Lists all resources in the given path (recursively). Only resources and no 059 * resource folders will be returned. 060 * 061 * @param path 062 * The path to list resources from. An empty string means the root of 063 * the contextClass' package. 064 */ 065 public static List<Resource> listResourcesRecursively(Class<?> contextClass, String path) { 066 try { 067 return listResources(contextClass, path, true); 068 } catch (IOException e) { 069 throw ResourceException.newListFailed(path, contextClass, e); 070 } 071 } 072 073 /** 074 * Gets the path to resources of given context class (relative to 075 * class-resources folder). 076 * 077 * @param contextClass 078 * The class for which path to resources should be returned 079 */ 080 public static String getPackageResourcePath(Class<?> contextClass) { 081 return contextClass.getPackage().getName().replace('.', FileSystemUtils.UNIX_SEPARATOR); 082 } 083 084 /** 085 * Lists resources in the given path and wraps them into {@link Resource} 086 * objects. 087 */ 088 private static List<Resource> listResources(Class<?> contextClass, String path, boolean recursive) 089 throws IOException { 090 return CollectionUtils.map(listResourceNames(contextClass, path, recursive), 091 resourcePath -> Resource.of(contextClass, resourcePath)); 092 } 093 094 /** 095 * Lists the sub-paths contained in the given path relative to the contextClass. 096 */ 097 private static List<String> listResourceNames(Class<?> contextClass, String path, boolean recursive) 098 throws IOException { 099 Set<URL> resourceContainers = getContainers(contextClass, path); 100 Set<String> resources = new HashSet<>(); 101 for (URL containerUrl : resourceContainers) { 102 List<String> paths = CollectionUtils.filterAndMap( 103 FileSystemUtils.listFilesInSameLocationForURL(containerUrl, recursive), 104 ResourceUtils::isNoClassFile, subResourcePath -> getResourcePath(path, subResourcePath)); 105 resources.addAll(paths); 106 } 107 return new ArrayList<>(resources); 108 } 109 110 public static Resource getFileBackedResource(Resource resource) { 111 Set<URL> validResourceUrls = getValidAlternativeUrls(resource.getUrl()); 112 if (validResourceUrls.isEmpty()) { 113 return resource; 114 } else { 115 URL anyUrl = CollectionUtils.getAny(validResourceUrls); 116 return Resource.of(resource.getContextClass(), resource.getPath(), anyUrl); 117 } 118 } 119 120 /** 121 * Returns all root URLs that could contain content for the package name of the 122 * contextClass. For the distribution this is always the jar file path of the 123 * class, because the resources are packaged together with the class. In a dev 124 * environment the classpath entries for the classes and resources are 125 * different. We therefore guess based on the class' directory where to find the 126 * corresponding test resources. For production resources we currently still 127 * enforce that they result in the same directory as the classes 128 * (ProjectJavaPlugin). 129 */ 130 private static Set<URL> getContainers(Class<?> contextClass, String path) throws IOException { 131 URL resourceUrl = contextClass.getResource(path); 132 if (resourceUrl == null) { 133 throw new FileNotFoundException(path + " does not exist in " + contextClass.getPackage().getName()); 134 } 135 Set<URL> resourceContainers = getValidAlternativeUrls(resourceUrl); 136 resourceContainers.add(resourceUrl); 137 return resourceContainers; 138 } 139 140 private static Set<URL> getValidAlternativeUrls(URL resourceUrl) { 141 Set<URL> resourceContainers = new HashSet<>(); 142 // Eclipse 143 appendIfValid(resourceUrl, resourceContainers, "/bin/main/", "/class-resources/"); 144 appendIfValid(resourceUrl, resourceContainers, "/bin/test/", "/test-data/"); 145 appendIfValid(resourceUrl, resourceContainers, "/bin/test/", "/test-utils-data/"); 146 appendIfValid(resourceUrl, resourceContainers, "/bin/test/", "/class-resources/"); 147 148 // IntelliJ 149 appendIfValid(resourceUrl, resourceContainers, "/out/main/classes/", "/class-resources/"); 150 appendIfValid(resourceUrl, resourceContainers, "/out/test/classes/", "/test-data/"); 151 appendIfValid(resourceUrl, resourceContainers, "/out/test/classes/", "/test-utils-data/"); 152 appendIfValid(resourceUrl, resourceContainers, "/out/test/classes/", "/class-resources/"); 153 154 // Gradle 155 appendIfValid(resourceUrl, resourceContainers, "/build/classes/java/main/", "/class-resources/"); 156 appendIfValid(resourceUrl, resourceContainers, "/build/classes/java/test/", "/test-data/"); 157 appendIfValid(resourceUrl, resourceContainers, "/build/classes/java/test/", "/test-utils-data/"); 158 appendIfValid(resourceUrl, resourceContainers, "/build/classes/java/test/", "/class-resources/"); 159 160 return resourceContainers; 161 } 162 163 /** 164 * Appends the baseUrl after replacing replacePattern with alternativeDir if it 165 * exists to resourceContainers. 166 */ 167 private static void appendIfValid(URL baseUrl, Set<URL> resourceContainers, String replacePattern, 168 String alternativeDir) { 169 if (!"file".equals(baseUrl.getProtocol()) || !baseUrl.toString().contains(replacePattern)) { 170 return; 171 } 172 try { 173 URL url = new URL(baseUrl.toString().replace(replacePattern, alternativeDir)); 174 if (new File(url.toURI()).exists()) { 175 resourceContainers.add(url); 176 } 177 } catch (MalformedURLException | URISyntaxException e) { 178 // Ignore if the directory does not exist 179 } 180 } 181 182 /** 183 * Concatenates path and subResourcePath leaving out path in case it is empty. 184 */ 185 private static String getResourcePath(String path, String subResourcePath) { 186 if (!path.isEmpty()) { 187 return path + FileSystemUtils.UNIX_SEPARATOR + subResourcePath; 188 } 189 return subResourcePath; 190 } 191 192 private static boolean isNoClassFile(String subResourcePath) { 193 return !subResourcePath.endsWith(CLASS_FILE_EXTENSION); 194 } 195}