protected void collect(Resource resource, List<ModuleDefinition> holder) throws IOException {
		// ResourcePatternResolver.getResources() may return resources that don't exist when not using wildcards
		if (!resource.exists()) {
			return;
		}

		String path = resource.getURI().toString();
		if (path.endsWith(HASH_EXTENSION)) {
			return;
		}
		else if (path.endsWith(ARCHIVE_AS_FILE_EXTENSION) && requireHashFiles && !hashResource(resource).exists()) {
			return;
		}
		// URI paths for directories include an extra slash, strip it for now
		if (path.endsWith("/")) {
			path = path.substring(0, path.length() - 1);
		}

		int lastSlash = path.lastIndexOf('/');
		int nextToLastSlash = path.lastIndexOf('/', lastSlash - 1);

		String name = path.substring(lastSlash + 1);
		if (name.endsWith(ARCHIVE_AS_FILE_EXTENSION)) {
			name = name.substring(0, name.length() - ARCHIVE_AS_FILE_EXTENSION.length());
		}
		String typeAsString = path.substring(nextToLastSlash + 1, lastSlash);
		ModuleType type = null;
		try {
			type = ModuleType.valueOf(typeAsString);
		}
		catch (IllegalArgumentException e) {
			// Not an actual type name, skip
			return;
		}
		String locationToUse = resource.getURL().toString();
		if (!locationToUse.endsWith(ARCHIVE_AS_FILE_EXTENSION) && !locationToUse.endsWith("/")) {
			locationToUse += "/";
		}

		SimpleModuleDefinition found = ModuleDefinitions.simple(name, type, locationToUse);

		if (holder.contains(found)) {
			SimpleModuleDefinition other = (SimpleModuleDefinition) holder.get(holder.indexOf(found));
			throw new IllegalStateException(String.format("Duplicate module definitions for '%s:%s' found at '%s' and" +
							" " +
							"'%s'",
					found.getType(), found.getName(), found.getLocation(), other.getLocation()));
		}
		else {
			holder.add(found);
		}
	}
Whether to require the presence of a hash file for archives. Helps preventing half-uploaded archives from being
picked up.
spring-xd-dirt/src/main/java/org/springframework/xd/dirt/module/ResourceModuleRegistry.java