Comment lister les fichiers dans un fichier JAR?


114

J'ai ce code qui lit tous les fichiers d'un répertoire.

    File textFolder = new File("text_directory");

    File [] texFiles = textFolder.listFiles( new FileFilter() {
           public boolean accept( File file ) {
               return file.getName().endsWith(".txt");
           }
    });

Cela fonctionne très bien. Il remplit le tableau avec tous les fichiers qui se terminent par ".txt" du répertoire "text_directory".

Comment puis-je lire le contenu d'un répertoire de la même manière dans un fichier JAR?

Donc, ce que je veux vraiment faire, c'est lister toutes les images dans mon fichier JAR, afin que je puisse les charger avec:

ImageIO.read(this.getClass().getResource("CompanyLogo.png"));

(Celui-ci fonctionne car le "CompanyLogo" est "codé en dur" mais le nombre d'images à l'intérieur du fichier JAR peut être de 10 à 200 de longueur variable.)

ÉDITER

Donc, je suppose que mon problème principal serait: Comment connaître le nom du fichier JAR où vit ma classe principale?

Certes, je pouvais le lire en utilisant java.util.Zip.

Ma structure est comme ceci:

Ils sont comme:

my.jar!/Main.class
my.jar!/Aux.class
my.jar!/Other.class
my.jar!/images/image01.png
my.jar!/images/image02a.png
my.jar!/images/imwge034.png
my.jar!/images/imagAe01q.png
my.jar!/META-INF/manifest 

À l'heure actuelle, je suis capable de charger par exemple "images / image01.png" en utilisant:

    ImageIO.read(this.getClass().getResource("images/image01.png));

Mais seulement parce que je connais le nom du fichier, pour le reste je dois les charger dynamiquement.


Juste une pensée - pourquoi ne pas compresser les images / jar dans un fichier séparé et lire les entrées de votre classe dans un autre jar?
Vineet Reynolds

3
Parce qu'il faudrait une étape "supplémentaire" pour la distribution / installation. :( Vous savez, les utilisateurs finaux.
OscarRyz

Étant donné que vous avez créé le fichier jar, vous pouvez aussi bien inclure la liste des fichiers qu'il contient plutôt que d'essayer des astuces.
Tom Hawtin - tackline

Eh bien, je me trompe peut-être, mais les pots peuvent être intégrés à d'autres pots. La solution de packaging one-jar (TM) ibm.com/developerworks/java/library/j-onejar fonctionne sur cette base. Sauf que dans votre cas, vous n'avez pas besoin des classes de charge de capacité.
Vineet Reynolds

Réponses:


91
CodeSource src = MyClass.class.getProtectionDomain().getCodeSource();
if (src != null) {
  URL jar = src.getLocation();
  ZipInputStream zip = new ZipInputStream(jar.openStream());
  while(true) {
    ZipEntry e = zip.getNextEntry();
    if (e == null)
      break;
    String name = e.getName();
    if (name.startsWith("path/to/your/dir/")) {
      /* Do something with this entry. */
      ...
    }
  }
} 
else {
  /* Fail... */
}

Notez que dans Java 7, vous pouvez créer un FileSystemfichier à partir du fichier JAR (zip), puis utiliser les mécanismes de navigation et de filtrage de répertoire de NIO pour le parcourir. Cela faciliterait l'écriture de code qui gère les JAR et les répertoires «éclatés».


hé merci ... je cherche un moyen de le faire depuis quelques heures maintenant !!
Newtopian

9
Oui, ce code fonctionne si nous voulons lister toutes les entrées dans ce fichier jar. Mais si je veux simplement lister un sous-répertoire dans le fichier jar, par exemple, example.jar / dir1 / dir2 / , comment puis-je lister directement tous les fichiers dans ce sous-répertoire? Ou j'ai besoin de décompresser ce fichier jar? J'apprécie beaucoup votre aide!
Ensom Hodder

L'approche Java 7 mentionnée est répertoriée dans la réponse de @ acheron55 .
Vadzim

@Vadzim Etes-vous sûr que la réponse de acheron55 est pour Java 7? Je n'ai pas trouvé Files.walk () ou java.util.Stream dans Java 7, mais dans Java 8: docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html
Bruce Sun

@BruceSun, dans java 7, vous pouvez utiliser Files.walkFileTree (...) à la place.
Vadzim

80

Code qui fonctionne pour les fichiers IDE et .jar:

import java.io.*;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.stream.*;

public class ResourceWalker {
    public static void main(String[] args) throws URISyntaxException, IOException {
        URI uri = ResourceWalker.class.getResource("/resources").toURI();
        Path myPath;
        if (uri.getScheme().equals("jar")) {
            FileSystem fileSystem = FileSystems.newFileSystem(uri, Collections.<String, Object>emptyMap());
            myPath = fileSystem.getPath("/resources");
        } else {
            myPath = Paths.get(uri);
        }
        Stream<Path> walk = Files.walk(myPath, 1);
        for (Iterator<Path> it = walk.iterator(); it.hasNext();){
            System.out.println(it.next());
        }
    }
}

5
FileSystems.newFileSystem()prend un Map<String, ?>, vous devez donc spécifier Collections.emptyMap()qu'il doit en renvoyer un correctement typé. Cela fonctionne: Collections.<String, Object>emptyMap().
Zero3

6
Fantastique!!! mais URI uri = MyClass.class.getResource ("/ resources"). toURI (); devrait avoir MyClass.class.getClassLoader (). getResource ("/ resources"). toURI (); c'est-à-dire getClassLoader (). Sinon, cela ne fonctionnait pas pour moi.
EMM

8
N'oubliez pas de fermer fileSystem!
gmjonker

3
Cela devrait être la première réponse pour la version 1.8 (la walkméthode dans Filesest uniquement disponible dans la version 1.8). Le seul problème est que le répertoire des ressources apparaît dans le Files.walk(myPath, 1), pas seulement les fichiers. Je suppose que le premier élément peut être simplement ignoré
toto_tico

4
myPath = fileSystem.getPath("/resources");ne fonctionne pas pour moi; il ne trouve rien. Cela devrait être "images" dans mon cas et le répertoire "images" est définitivement inclus dans mon pot!
phip1611

21

La réponse d' Erickson a parfaitement fonctionné:

Voici le code de travail.

CodeSource src = MyClass.class.getProtectionDomain().getCodeSource();
List<String> list = new ArrayList<String>();

if( src != null ) {
    URL jar = src.getLocation();
    ZipInputStream zip = new ZipInputStream( jar.openStream());
    ZipEntry ze = null;

    while( ( ze = zip.getNextEntry() ) != null ) {
        String entryName = ze.getName();
        if( entryName.startsWith("images") &&  entryName.endsWith(".png") ) {
            list.add( entryName  );
        }
    }

 }
 webimages = list.toArray( new String[ list.size() ] );

Et je viens de modifier ma méthode de chargement à partir de ceci:

File[] webimages = ... 
BufferedImage image = ImageIO.read(this.getClass().getResource(webimages[nextIndex].getName() ));

Pour ça:

String  [] webimages = ...

BufferedImage image = ImageIO.read(this.getClass().getResource(webimages[nextIndex]));

9

Je voudrais développer la réponse de acheron55 , car il s'agit d'une solution très peu sûre, pour plusieurs raisons:

  1. Cela ne ferme pas l' FileSystemobjet.
  2. Il ne vérifie pas si l' FileSystemobjet existe déjà.
  3. Ce n'est pas sûr pour les threads.

C'est une solution un peu plus sûre:

private static ConcurrentMap<String, Object> locks = new ConcurrentHashMap<>();

public void walk(String path) throws Exception {

    URI uri = getClass().getResource(path).toURI();
    if ("jar".equals(uri.getScheme()) {
        safeWalkJar(path, uri);
    } else {
        Files.walk(Paths.get(path));
    }
}

private void safeWalkJar(String path, URI uri) throws Exception {

    synchronized (getLock(uri)) {    
        // this'll close the FileSystem object at the end
        try (FileSystem fs = getFileSystem(uri)) {
            Files.walk(fs.getPath(path));
        }
    }
}

private Object getLock(URI uri) {

    String fileName = parseFileName(uri);  
    locks.computeIfAbsent(fileName, s -> new Object());
    return locks.get(fileName);
}

private String parseFileName(URI uri) {

    String schemeSpecificPart = uri.getSchemeSpecificPart();
    return schemeSpecificPart.substring(0, schemeSpecificPart.indexOf("!"));
}

private FileSystem getFileSystem(URI uri) throws IOException {

    try {
        return FileSystems.getFileSystem(uri);
    } catch (FileSystemNotFoundException e) {
        return FileSystems.newFileSystem(uri, Collections.<String, String>emptyMap());
    }
}   

Il n'y a pas vraiment besoin de synchroniser le nom du fichier; on pourrait simplement se synchroniser sur le même objet à chaque fois (ou faire la méthode synchronized), c'est purement une optimisation.

Je dirais que c'est toujours une solution problématique, car il peut y avoir d'autres parties du code qui utilisent l' FileSysteminterface sur les mêmes fichiers, et cela pourrait interférer avec eux (même dans une seule application threadée).
De plus, il ne vérifie pas les nulls (par exemple, on getClass().getResource().

Cette interface Java NIO particulière est plutôt horrible, car elle introduit une ressource globale / singleton non thread-safe, et sa documentation est extrêmement vague (beaucoup d'inconnues dues aux implémentations spécifiques du fournisseur). Les résultats peuvent varier pour d'autres FileSystemfournisseurs (pas pour JAR). Il y a peut-être une bonne raison à cela; Je ne sais pas, je n'ai pas recherché les implémentations.


1
La synchronisation des ressources externes, comme FS, n'a pas beaucoup de sens au sein d'une machine virtuelle. D'autres applications peuvent y accéder en dehors de votre VM. Outre même dans votre propre application, votre verrouillage basé sur les noms de fichiers peut être facilement contourné. Avec cela, il est préférable de s'appuyer sur des mécanismes de synchronisation du système d'exploitation, comme le verrouillage de fichiers.
Espinosa

@Espinosa Le mécanisme de verrouillage des noms de fichiers pourrait être totalement contourné; ma réponse n'est pas assez sûre non plus, mais je pense que c'est le maximum que vous pouvez obtenir avec Java NIO avec un minimum d'effort. S'appuyer sur le système d'exploitation pour gérer les verrous, ou ne pas contrôler quelles applications accèdent à quels fichiers est une mauvaise pratique à mon humble avis, à moins que vous ne construisiez une application basée sur les clients - par exemple, un éditeur de texte. Ne pas gérer vous-même les verrous entraînera la levée d'exceptions ou le blocage des threads de l'application - les deux doivent être évités.
Eyal Roth

8

Donc je suppose que mon principal problème serait de savoir comment connaître le nom du pot où vit ma classe principale.

En supposant que votre projet est emballé dans un Jar (pas nécessairement vrai!), Vous pouvez utiliser ClassLoader.getResource () ou findResource () avec le nom de la classe (suivi de .class) pour obtenir le fichier jar qui contient une classe donnée. Vous devrez analyser le nom du fichier à partir de l'URL renvoyée (pas si difficile), que je laisserai comme exercice au lecteur :-)

Assurez-vous de tester le cas où la classe ne fait pas partie d'un pot.


1
hein - intéressant que cela aurait été modifié sans commentaire ... Nous utilisons la technique ci-dessus tout le temps et cela fonctionne très bien.
Kevin Day

Un vieux problème, mais pour moi, cela semble être un bon hack. Upvoté à zéro :)
Tuukka Mustonen

Vote positif, car c'est la seule solution répertoriée ici pour le cas où une classe n'a pas de fichier CodeSource.
Réintégrer Monica 2331977

7

J'ai porté la réponse d' acheron55 sur Java 7 et fermé l' FileSystemobjet. Ce code fonctionne dans les IDE, dans les fichiers jar et dans un jar à l'intérieur d'une guerre sur Tomcat 7; mais notez que ça ne marche pas dans un bocal à l'intérieur d'une guerre sur JBoss 7 (ça donne FileSystemNotFoundException: Provider "vfs" not installed, voir aussi ce post ). De plus, comme le code original, il n'est pas thread-safe, comme suggéré par errr . Pour ces raisons, j'ai abandonné cette solution; cependant, si vous pouvez accepter ces problèmes, voici mon code prêt à l'emploi:

import java.io.IOException;
import java.net.*;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collections;

public class ResourceWalker {

    public static void main(String[] args) throws URISyntaxException, IOException {
        URI uri = ResourceWalker.class.getResource("/resources").toURI();
        System.out.println("Starting from: " + uri);
        try (FileSystem fileSystem = (uri.getScheme().equals("jar") ? FileSystems.newFileSystem(uri, Collections.<String, Object>emptyMap()) : null)) {
            Path myPath = Paths.get(uri);
            Files.walkFileTree(myPath, new SimpleFileVisitor<Path>() { 
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    System.out.println(file);
                    return FileVisitResult.CONTINUE;
                }
            });
        }
    }
}

5

Voici une méthode que j'ai écrite pour "exécuter tous les JUnits sous un package". Vous devriez pouvoir l'adapter à vos besoins.

private static void findClassesInJar(List<String> classFiles, String path) throws IOException {
    final String[] parts = path.split("\\Q.jar\\\\E");
    if (parts.length == 2) {
        String jarFilename = parts[0] + ".jar";
        String relativePath = parts[1].replace(File.separatorChar, '/');
        JarFile jarFile = new JarFile(jarFilename);
        final Enumeration<JarEntry> entries = jarFile.entries();
        while (entries.hasMoreElements()) {
            final JarEntry entry = entries.nextElement();
            final String entryName = entry.getName();
            if (entryName.startsWith(relativePath)) {
                classFiles.add(entryName.replace('/', File.separatorChar));
            }
        }
    }
}

Edit: Ah, dans ce cas, vous voudrez peut-être aussi cet extrait (même cas d'utilisation :))

private static File findClassesDir(Class<?> clazz) {
    try {
        String path = clazz.getProtectionDomain().getCodeSource().getLocation().getFile();
        final String codeSourcePath = URLDecoder.decode(path, "UTF-8");
        final String thisClassPath = new File(codeSourcePath, clazz.getPackage().getName().repalce('.', File.separatorChar));
    } catch (UnsupportedEncodingException e) {
        throw new AssertionError("impossible", e);
    }
}

1
Je suppose que le gros problème est de connaître le nom du fichier jar en premier lieu. C'est le bocal où vit la classe principale.
OscarRyz

5

Voici un exemple d'utilisation de la bibliothèque Reflections pour analyser de manière récursive le chemin de classe par modèle de nom regex augmenté de quelques avantages Guava pour récupérer le contenu des ressources:

Reflections reflections = new Reflections("com.example.package", new ResourcesScanner());
Set<String> paths = reflections.getResources(Pattern.compile(".*\\.template$"));

Map<String, String> templates = new LinkedHashMap<>();
for (String path : paths) {
    log.info("Found " + path);
    String templateName = Files.getNameWithoutExtension(path);
    URL resource = getClass().getClassLoader().getResource(path);
    String text = Resources.toString(resource, StandardCharsets.UTF_8);
    templates.put(templateName, text);
}

Cela fonctionne avec les bocaux et les classes éclatées.


Attention, Reflections ne prend toujours pas en charge Java 9 et supérieur: github.com/ronmamo/reflections/issues/186 . Il existe des liens vers des bibliothèques concurrentes.
Vadzim

3

Un fichier jar est juste un fichier zip avec un manifeste structuré. Vous pouvez ouvrir le fichier jar avec les outils java zip habituels et analyser le contenu du fichier de cette façon, gonfler les flux, etc.

EDIT / après clarification

Il m'a fallu une minute pour me souvenir de tous les éléments et je suis sûr qu'il existe des moyens plus propres de le faire, mais je voulais voir que je n'étais pas fou. Dans mon projet, image.jpg est un fichier dans une partie du fichier jar principal. J'obtiens le chargeur de classe de la classe principale (SomeClass est le point d'entrée) et l'utilise pour découvrir la ressource image.jpg. Ensuite, un peu de magie de flux pour l'introduire dans cette chose ImageInputStream et tout va bien.

InputStream inputStream = SomeClass.class.getClassLoader().getResourceAsStream("image.jpg");
JPEGImageReaderSpi imageReaderSpi = new JPEGImageReaderSpi();
ImageReader ir = imageReaderSpi.createReaderInstance();
ImageInputStream iis = new MemoryCacheImageInputStream(inputStream);
ir.setInput(iis);
....
ir.read(0); //will hand us a buffered image

Ce jar contient le programme principal et les ressources. Comment faire référence au pot auto? à partir du fichier jar?
OscarRyz

Pour faire référence au fichier JAR, utilisez simplement "blah.JAR" comme chaîne. Vous pouvez utiliser new File("blah.JAR")pour créer un objet File qui représente le JAR, par exemple. Remplacez simplement "blah.JAR" par le nom de votre JAR.
Thomas Owens

Si c'est le même jar dont vous êtes déjà à court, le chargeur de classe devrait pouvoir voir des choses à l'intérieur du jar ... J'ai mal compris ce que vous essayiez de faire au départ.
Mikeb

2
Eh bien oui, j'ai déjà ça, le problème est quand j'ai besoin de quelque chose comme: "... getResourceAsStream (" *. Jpg "); ..." C'est, dynamiquement, lister les fichiers contenus.
OscarRyz

3

Étant donné un fichier JAR réel, vous pouvez répertorier le contenu à l'aide de JarFile.entries(). Cependant, vous aurez besoin de connaître l'emplacement du fichier JAR - vous ne pouvez pas simplement demander au chargeur de classe de répertorier tout ce qu'il pourrait atteindre.

Vous devriez être en mesure de déterminer l'emplacement du fichier JAR en fonction de l'URL renvoyée ThisClassName.class.getResource("ThisClassName.class"), mais cela peut être un peu compliqué.


En lisant votre réponse, une autre question s'est posée. Qu'est-ce qui donnerait l'appel: this.getClass (). GetResource ("/ my_directory"); Il doit renvoyer une URL qui pourrait à son tour être ... utilisée comme répertoire? Nahh ... laissez-moi essayer.
OscarRyz

Vous connaissez toujours l'emplacement du JAR - il est dans ".". Tant que le nom du JAR est connu pour être quelque chose, vous pouvez utiliser une constante String quelque part. Maintenant, si les gens vont changer le nom du JAR ...
Thomas Owens

@Thomas: Cela suppose que vous exécutez l'application à partir du répertoire actuel. Quel est le problème avec "java -jar foo / bar / baz.jar"?
Jon Skeet

Je crois (et je devrais vérifier) ​​que si vous aviez du code dans votre Jar new File("baz.jar), l'objet File représenterait votre fichier JAR.
Thomas Owens

@Thomas: Je ne le crois pas. Je pense que ce serait relatif au répertoire de travail actuel du processus. Je devrais vérifier aussi :)
Jon Skeet

3

Il y a quelque temps, j'ai créé une fonction qui obtient des classes à partir de JAR:

public static Class[] getClasses(String packageName) 
throws ClassNotFoundException{
    ArrayList<Class> classes = new ArrayList<Class> ();

    packageName = packageName.replaceAll("\\." , "/");
    File f = new File(jarName);
    if(f.exists()){
        try{
            JarInputStream jarFile = new JarInputStream(
                    new FileInputStream (jarName));
            JarEntry jarEntry;

            while(true) {
                jarEntry=jarFile.getNextJarEntry ();
                if(jarEntry == null){
                    break;
                }
                if((jarEntry.getName ().startsWith (packageName)) &&
                        (jarEntry.getName ().endsWith (".class")) ) {
                    classes.add(Class.forName(jarEntry.getName().
                            replaceAll("/", "\\.").
                            substring(0, jarEntry.getName().length() - 6)));
                }
            }
        }
        catch( Exception e){
            e.printStackTrace ();
        }
        Class[] classesA = new Class[classes.size()];
        classes.toArray(classesA);
        return classesA;
    }else
        return null;
}

2
public static ArrayList<String> listItems(String path) throws Exception{
    InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(path);
    byte[] b = new byte[in.available()];
    in.read(b);
    String data = new String(b);
    String[] s = data.split("\n");
    List<String> a = Arrays.asList(s);
    ArrayList<String> m = new ArrayList<>(a);
    return m;
}

3
Bien que cet extrait de code puisse résoudre le problème, il n'explique pas pourquoi ni comment il répond à la question. Veuillez inclure une explication de votre code , car cela contribue vraiment à améliorer la qualité de votre message. N'oubliez pas que vous répondez à la question aux lecteurs à l'avenir, et que ces personnes pourraient ne pas connaître les raisons de votre suggestion de code.
Samuel Philipp

les données sont vides lorsque nous exécutons le code à partir d'un fichier jar.
Aguid


1

Le mécanisme le plus robuste pour lister toutes les ressources dans le chemin de classe consiste actuellement à utiliser ce modèle avec ClassGraph , car il gère le plus large éventail possible de mécanismes de spécification de chemin de classe, y compris le nouveau système de module JPMS. (Je suis l'auteur de ClassGraph.)

Comment connaître le nom du fichier JAR où vit ma classe principale?

URI mainClasspathElementURI;
try (ScanResult scanResult = new ClassGraph().whitelistPackages("x.y.z")
        .enableClassInfo().scan()) {
    mainClasspathElementURI =
            scanResult.getClassInfo("x.y.z.MainClass").getClasspathElementURI();
}

Comment puis-je lire le contenu d'un répertoire de la même manière dans un fichier JAR?

List<String> classpathElementResourcePaths;
try (ScanResult scanResult = new ClassGraph().overrideClasspath(mainClasspathElementURI)
        .scan()) {
    classpathElementResourcePaths = scanResult.getAllResources().getPaths();
}

Il existe également de nombreuses autres façons de gérer les ressources .


1
Très joli package, facilement utilisable dans mon projet Scala, merci.
zslim le

0

Juste une manière différente de lister / lire des fichiers à partir d'une URL de jar et il le fait de manière récursive pour les jars imbriqués

https://gist.github.com/trung/2cd90faab7f75b3bcbaa

URL urlResource = Thead.currentThread().getContextClassLoader().getResource("foo");
JarReader.read(urlResource, new InputStreamCallback() {
    @Override
    public void onFile(String name, InputStream is) throws IOException {
        // got file name and content stream 
    }
});

0

Un de plus pour la route:

import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.List;

import static java.nio.file.FileSystems.newFileSystem;
import static java.util.Collections.emptyMap;

public class ResourceWalker {
  private static final PathMatcher FILE_MATCHER =
      FileSystems.getDefault().getPathMatcher( "glob:**.ttf" );

  public static List<Path> walk( final String directory )
      throws URISyntaxException, IOException {
    final List<Path> filenames = new ArrayList<>();
    final var resource = ResourceWalker.class.getResource( directory );

    if( resource != null ) {
      final var uri = resource.toURI();
      final var path = uri.getScheme().equals( "jar" )
          ? newFileSystem( uri, emptyMap() ).getPath( directory )
          : Paths.get( uri );
      final var walk = Files.walk( path, 10 );

      for( final var it = walk.iterator(); it.hasNext(); ) {
        final Path p = it.next();
        if( FILE_MATCHER.matches( p ) ) {
          filenames.add( p );
        }
      }
    }

    return filenames;
  }
}

C'est un peu plus flexible pour faire correspondre des noms de fichiers spécifiques car il utilise le globbing générique.


Un style plus fonctionnel:

import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.*;
import java.util.function.Consumer;

import static java.nio.file.FileSystems.newFileSystem;
import static java.util.Collections.emptyMap;

/**
 * Responsible for finding file resources.
 */
public class ResourceWalker {
  private static final PathMatcher FILE_MATCHER =
      FileSystems.getDefault().getPathMatcher( "glob:**.ttf" );

  public static void walk( final String dirName, final Consumer<Path> f )
      throws URISyntaxException, IOException {
    final var resource = ResourceWalker.class.getResource( dirName );

    if( resource != null ) {
      final var uri = resource.toURI();
      final var path = uri.getScheme().equals( "jar" )
          ? newFileSystem( uri, emptyMap() ).getPath( dirName )
          : Paths.get( uri );
      final var walk = Files.walk( path, 10 );

      for( final var it = walk.iterator(); it.hasNext(); ) {
        final Path p = it.next();
        if( FILE_MATCHER.matches( p ) ) {
          f.accept( p );
        }
      }
    }
  }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.