package com.redhat.ceylon.common.tools; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.redhat.ceylon.common.Backend; import com.redhat.ceylon.common.Constants; import com.redhat.ceylon.common.FileUtil; import com.redhat.ceylon.common.ModuleDescriptorReader; /** * Class with helper methods for expanding a list of module names/specs * that possibly contain wildcards into an expanded list of actual modules * that were found on the file system * @author Tako Schotanus */ public abstract class ModuleWildcardsHelper { private static final Pattern idPattern = Pattern.compile("\\p{IsLowercase}[\\p{IsAlphabetic}\\p{IsDigit}_]*"); /** * Given a source directory and a list of ModuleSpecs * that possibly contain wildcards it returns a expanded list of * ModuleSpecs of modules that were actually found in the given * source directory. ModuleSpecs that didn't contain wildcards * are left alone (it's not checked if they exist or not). * If a Backend is passed expanded modules will be checked if * they support it (they either don't have a native annotation * or it is for the correct backend). * @param dirs The source directory * @param names The list of ModuleSpecs * @param forBackend The Backend for which we work or null * @return An expanded list of ModuleSpecs */ public static List<ModuleSpec> expandSpecWildcards(File dir, List<ModuleSpec> modules, Backend forBackend) { List<File> dirs = new ArrayList<File>(); dirs.add(dir); return expandSpecWildcards(dirs, modules, forBackend); } /** * Given a list of source directories and a list of ModuleSpecs * that possibly contain wildcards it returns a expanded list of * ModuleSpecs of modules that were actually found in the given * source directories. ModuleSpecs that didn't contain wildcards * are left alone (it's not checked if they exist or not). * If a Backend is passed expanded modules will be checked if * they support it (they either don't have a native annotation * or it is for the correct backend). * @param dirs The list of source directories * @param names The list of ModuleSpecs * @param forBackend The Backend for which we work or null * @return An expanded list of ModuleSpecs */ public static List<ModuleSpec> expandSpecWildcards(List<File> dirs, List<ModuleSpec> modules, Backend forBackend) { List<ModuleSpec> result = new ArrayList<ModuleSpec>(modules.size()); for (ModuleSpec spec : modules) { List<String> names = new ArrayList<String>(); expandWildcard(names, dirs, spec.getName(), forBackend); for (String name : names) { result.add(new ModuleSpec(name, spec.getVersion())); } } return result; } /** * Given a source directory and a list of module names * that possibly contain wildcards it returns a expanded list of * module names of modules that were actually found in the given * source directory. Module names that didn't contain wildcards * are left alone (it's not checked if they exist or not). * If a Backend is passed expanded modules will be checked if * they support it (they either don't have a native annotation * or it is for the correct backend). * @param dirs The source directory * @param names The list of module names * @param forBackend The Backend for which we work or null * @return An expanded list of module names */ public static List<String> expandWildcards(File dir, List<String> modules, Backend forBackend) { List<File> dirs = new ArrayList<File>(); dirs.add(dir); return expandWildcards(dirs, modules, forBackend); } /** * Given a list of source directories and a list of module names * that possibly contain wildcards it returns a expanded list of * module names of modules that were actually found in the given * source directories. Module names that didn't contain wildcards * are left alone (it's not checked if they exist or not). * If a Backend is passed expanded modules will be checked if * they support it (they either don't have a native annotation * or it is for the correct backend). * @param dirs The list of source directories * @param names The list of module names * @param forBackend The Backend for which we work or null * @return An expanded list of module names */ public static List<String> expandWildcards(Iterable<File> dirs, List<String> names, Backend forBackend) { List<String> result = new ArrayList<String>(names.size()); for (String name : names) { expandWildcard(result, dirs, name, forBackend); } return result; } public static void expandWildcard(List<String> result, Iterable<File> dirs, String name, Backend forBackend) { if (name.endsWith("*")) { int p = name.lastIndexOf('.'); String parentPath = "."; String namePrefix = ""; if (p >= 0) { String parentName = name.substring(0, p); parentPath = parentName.replace('.', File.separatorChar); namePrefix = name.substring(p + 1, name.length() - 1); } else { namePrefix = name.substring(0, name.length() - 1); } List<String> modules = findModules(dirs, parentPath, namePrefix, forBackend); result.addAll(modules); } else { result.add(name); } } public static boolean isValidModuleDir(Iterable<File> dirs, String name) { if (isModuleName(name)) { String path = name.replace('.', File.separatorChar); if (existsSourceSubDir(dirs, path)) { return true; } } return false; } public static boolean isModuleName(String name) { String[] parts = name.split("\\."); for (String part : parts) { if (part.isEmpty()) { return false; } Matcher m = idPattern.matcher(part); if (!m.matches()) { return false; } } return true; } private static boolean existsSourceSubDir(Iterable<File> dirs, String file) { for (File dir : dirs) { File subDir = new File(dir, file); if (subDir.isDirectory() && subDir.canRead()) { return true; } } return false; } private static List<String> findModules(Iterable<File> dirs, String modPath, String prefix, Backend forBackend) { List<String> modules = new ArrayList<String>(); for (File dir : dirs) { File modDir = new File(dir, modPath); if (modDir.isDirectory() && modDir.canRead()) { findModules(modules, dir, modDir, prefix, forBackend, true); } } return modules; } private static void findModules(List<String> modules, File root, File dir, String prefix, Backend forBackend, boolean first) { File descriptor = new File(dir, Constants.MODULE_DESCRIPTOR); if (descriptor.isFile()) { File modDir = FileUtil.relativeFile(root, dir); String modName = modDir.getPath().replace(File.separatorChar, '.'); if (includeModule(modName, root, prefix, forBackend)) { modules.add(modName); } } else if (first || prefix == null || prefix.isEmpty()) { File[] files = dir.listFiles(); for (File f : files) { if (f.isDirectory() && f.canRead() && isModuleName(f.getName())) { findModules(modules, root, f, prefix, forBackend, false); } } } } private static boolean includeModule(String name, File sourceRoot, String prefix, Backend forBackend) { if (prefix != null) { int p = name.lastIndexOf('.'); String lastPart = (p >= 0) ? name.substring(p + 1) : name; if (!lastPart.startsWith(prefix)) { return false; } } if (forBackend != null) { try { ModuleDescriptorReader mdr = new ModuleDescriptorReader(name, sourceRoot); String backend = mdr.getModuleBackend(); return (backend == null) || backend.equals(forBackend.nativeAnnotation); } catch(ModuleDescriptorReader.NoSuchModuleException x) { x.printStackTrace(); } } return true; } }