javaantantbuilder

Directory turns into file after ant build. How to handle that?


The method below is supposed to read classes/files out of a certain directory. It works alright when debugging the method in eclipse. But after building it using ant the scannedDir is not a directory anymore. Does anyone know how to change it so that it still works after deployment? Thanks in advance!

private static List<Class<?>> find(String scannedPackage) {

    logger.trace("Scanned package='{}'", scannedPackage);
    String scannedPath = scannedPackage.replace(DOT, SLASH);
    logger.trace("Scanned path='{}'", scannedPath);
    URL scannedUrl = Thread.currentThread().getContextClassLoader().getResource(scannedPath);
    logger.trace("Scanned url='{}'", scannedUrl);

    if (scannedUrl == null) {
        throw new IllegalArgumentException(String.format(BAD_PACKAGE_ERROR, scannedPath, scannedPackage));
    }

    File scannedDir = new File(scannedUrl.getFile());
    logger.trace("scannedDir.isDirecory='{}'", scannedDir.isDirectory());
    List<Class<?>> classes = new ArrayList<Class<?>>();

    for (File file : scannedDir.listFiles()) {
        logger.trace("currentFile='{}'", file.getAbsolutePath());
        classes.addAll(find(file, scannedPackage));
    }

    return classes;
}

So in detail: When I run it using eclipse debug mode scannedDir.isDirectory=true. But after building it scannedDir.isDirectory=false

Cheers!

EDIT:

scannedDir.getAbsolutePath() returns jar:file:/C:/Users/xxx.xxx/AppData/Local/XBRLGEN/app/xbrl_s2.jar!/eu/europa/eiopa/xbrl/s2c/dict/dom


Solution

  • You can’t treat entries in a jar file like Files, that will never work. You are also misinterpreting the problem. Just because the File says that it isn’t a directory doesn’t imply that it is an ordinary file, it simply doesn’t exist as you have extracted a part of a jar: URL and interpreted as an ordinary file.

    Here is a solution of your task working on an abstraction that doesn’t require ordinary files:

    private static Set<Class<?>> getClasses(String pkg)
      throws IOException, URISyntaxException {
        ClassLoader cll = Thread.currentThread().getContextClassLoader();
    
        URI clURI = cll.getResource(pkg.replace('.', '/')).toURI();
        if(!clURI.getScheme().equals("file")) try {
            FileSystems.getFileSystem(clURI);
        } catch(FileSystemNotFoundException ex) {
            FileSystems.newFileSystem(clURI, Collections.emptyMap());
        }
        return Files.list(Paths.get(clURI))
            .map(p->p.getFileName().toString())
            .filter(s->s.endsWith(".class"))
            .map(s->s.substring(0, s.length()-6))
            .map(s-> { try {
                return cll.loadClass(pkg+'.'+s);
              } catch(ClassNotFoundException ex) { return null; } })
            .filter(Objects::nonNull)
            .collect(Collectors.toSet());
    }
    

    but it is unreliable as jar files are not required to have entries for directories at all. In the case of absent entries, directories exist only implied due to the fact that an entry of a regular file inside that directory exist, e.g. an entry foo/bar implies that there is a directory foo. In this case, getResource attempts for the directory will fail.

    The safest way to query a package is to pick one class inside that package and perform a lookup on it as the associated resource definitely exist. Then you can traverse the parent of it, even if its existence is only implied:

    private static Set<Class<?>> getClasses(Class<?> context)
        throws IOException, URISyntaxException {
    
        ClassLoader cll = context.getClassLoader();
    
        URI clURI = context.getResource(context.getSimpleName()+".class").toURI();
        if(!clURI.getScheme().equals("file")) try {
            FileSystems.getFileSystem(clURI);
        } catch(FileSystemNotFoundException ex) {
            FileSystems.newFileSystem(clURI, Collections.emptyMap());
        }
        String pkg=context.getPackage().getName();
        return Files.list(Paths.get(clURI).getParent())
            .map(p->p.getFileName().toString())
            .filter(s->s.endsWith(".class"))
            .map(s->s.substring(0, s.length()-6))
            .map(s-> { try {
                return Class.forName(pkg+'.'+s, false, cll);
              } catch(ClassNotFoundException ex) { return null; } })
            .filter(Objects::nonNull)
            .collect(Collectors.toSet());
    }