Comment redémarrer une application Java?


94

Comment redémarrer une application Java AWT? J'ai un bouton auquel j'ai attaché un gestionnaire d'événements. Quel code dois-je utiliser pour redémarrer l'application?

Je veux faire la même chose que Application.Restart()dans une application C #.


2
Peut-être que je ne comprends pas votre question. Vous voulez que votre application ait un bouton qui redémarre l'application? Donc, une fois que l'application n'est plus en cours d'exécution, elle devrait pouvoir se redémarrer? Cela me semble impossible.
Jay

Je ne demande pas cela après l'arrêt de la JVM, je demande comment puis-je réapparaître ma trame principale Java?
Azfar Niaz

2
Pas impossible. Je vois que l'atelier eclipse redémarre fréquemment, même Windows fait cette astuce après les mises à jour. La fausse hypothèse est que l'application est la seule chose qui fonctionne sans rien en dessous. Nous aurons besoin d'un lanceur capable de redémarrer, des tortues tout en bas.
whatnick

juste de la même manière que dans l'application C #, où vous pouvez écrire System.restart () pour le faire?
Azfar Niaz

@aniaz, vous devez mettre à jour la question pour indiquer que vous souhaitez afficher / masquer le cadre. L'application n'est PAS le cadre.
whatnick

Réponses:


105

Bien entendu, il est possible de redémarrer une application Java.

La méthode suivante montre un moyen de redémarrer une application Java:

public void restartApplication()
{
  final String javaBin = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
  final File currentJar = new File(MyClassInTheJar.class.getProtectionDomain().getCodeSource().getLocation().toURI());

  /* is it a jar file? */
  if(!currentJar.getName().endsWith(".jar"))
    return;

  /* Build command: java -jar application.jar */
  final ArrayList<String> command = new ArrayList<String>();
  command.add(javaBin);
  command.add("-jar");
  command.add(currentJar.getPath());

  final ProcessBuilder builder = new ProcessBuilder(command);
  builder.start();
  System.exit(0);
}

En gros, il fait ce qui suit:

  1. Trouvez l'exécutable java (j'ai utilisé le binaire java ici, mais cela dépend de vos besoins)
  2. Trouvez l'application (un pot dans mon cas, en utilisant la MyClassInTheJarclasse pour trouver l'emplacement du pot lui-même)
  3. Créez une commande pour redémarrer le jar (en utilisant le binaire java dans ce cas)
  4. L'exécuter! (et ainsi mettre fin à l'application en cours et la redémarrer)

5
N'y a-t-il pas un petit laps de temps pendant lequel deux versions de la même application s'exécutent en même temps?
Monir

5
System.exit (0) ne terminera-t-il pas le processus enfant?
Horcrux7

16
@Veger Question si System.exit(0)termine le processus enfant a la même réponse que si cette réponse fonctionne vraiment et pourquoi. Si vous ne pouvez pas fournir une explication raisonnable avec votre réponse, vous avez fait un mauvais travail. Une réponse qui fournit plus de questions qu'elle ne répond n'est pas un exemple de réponse approfondie. Les bonnes réponses ne montrent pas seulement le code, mais expliquent également comment et pourquoi ils fonctionnent, quels sont les inconvénients et quelles sont les alternatives. Vous n'avez même pas essayé de couvrir ces choses.
Tomáš Zato - Réintégrer Monica

8
Autant de commentaires débattant de savoir s'il faut répondre à la question de @ Horcrux7 ou non. Vous auriez pu lui dire la réponse depuis le début lol. Eh bien, je vais aller de l'avant et le faire (un peu tard je sais): non, ce n'est pas le cas. Là.
Voldemort

10
Pour répondre à mes questions moi-même. L'échantillon ne fonctionne pas !!! System.exit (0) met immédiatement fin au processus client.
Horcrux 7

35
import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;

public class Main {
    public static void main(String[] args) throws IOException, InterruptedException {
        StringBuilder cmd = new StringBuilder();
        cmd.append(System.getProperty("java.home") + File.separator + "bin" + File.separator + "java ");
        for (String jvmArg : ManagementFactory.getRuntimeMXBean().getInputArguments()) {
            cmd.append(jvmArg + " ");
        }
        cmd.append("-cp ").append(ManagementFactory.getRuntimeMXBean().getClassPath()).append(" ");
        cmd.append(Main.class.getName()).append(" ");
        for (String arg : args) {
            cmd.append(arg).append(" ");
        }
        Runtime.getRuntime().exec(cmd.toString());
        System.exit(0);
    }
}

Dédié à tous ceux qui disent que c'est impossible.

Ce programme collecte toutes les informations disponibles pour reconstruire la ligne de commande d'origine. Ensuite, il le lance et comme il s'agit de la même commande, votre application démarre une seconde fois. Ensuite, nous quittons le programme d'origine, le programme enfant reste en cours d'exécution (même sous Linux) et fait exactement la même chose.

AVERTISSEMENT : si vous exécutez ceci, sachez qu'il ne s'arrête jamais de créer de nouveaux processus, similaires à une bombe à fourche .


Une amélioration possible ManagementFactory.getRuntimeMXBean().getInputArguments() ne vous donnera que les arguments d'entrée passés à la JVM. Il manque les paramètres passés à votre application. par exemple java -jar start.jar -MISSED_PARAM=true. Sur un jvm oracle, vous pouvez récupérer ces paramètres à l'aide de System.getProperty("sun.java.command").
Chris2M

1
La machine virtuelle parente peut s'arrêter si la machine virtuelle enfant et la machine virtuelle parente ne sont pas connectées l'une à l'autre avec des canaux, ce qui se produit lors du démarrage de la machine virtuelle enfant. En utilisant ProcessBuilderet inheritIO(), la machine virtuelle enfant peut être démarrée de manière à ce que la machine virtuelle parente se termine.
Christian Hujer

1
J'ai une version de ça. Ce commentaire est pour vous dire comment l'arrêter: renommez quelque chose dans le chemin qui contient le java.exe.
Dale

Il ne s'agit pas à proprement parler de redémarrer mais de lancer une nouvelle JVM avec les mêmes arguments que celle-ci.
Thorbjørn Ravn Andersen

4
Quelle est la différence? Y a-t-il une différence entre le redémarrage d'un PC et l'arrêt du système d'exploitation + le redémarrage?
Meinersbur

27

En gros, vous ne pouvez pas. Du moins pas de manière fiable. Cependant, vous ne devriez pas en avoir besoin.

Le ne peut pas se séparer

Pour redémarrer un programme Java, vous devez redémarrer la JVM. Pour redémarrer la JVM, vous devez

  1. Localisez le javalanceur utilisé. Vous pouvez essayer avec System.getProperty("java.home")mais il n'y a aucune garantie que cela pointera réellement vers le lanceur qui a été utilisé pour lancer votre application. (La valeur renvoyée peut ne pas pointer vers le JRE utilisé pour lancer l'application ou elle peut avoir été remplacée par -Djava.home.)

  2. Vous voulez sans doute pour honorer la mémoire des paramètres d' origine etc ( -Xmx, -Xms...) de sorte que vous devez déterminer les paramètres où utilisé pour démarrer la première machine virtuelle Java. Vous pouvez essayer d'utiliser, ManagementFactory.getRuntimeMXBean().getInputArguments()mais rien ne garantit que cela reflétera les paramètres utilisés. Ceci est même précisé dans la documentation de cette méthode:

    En règle générale, toutes les options de ligne de commande de la commande 'java' ne sont pas transmises à la machine virtuelle Java. Ainsi, les arguments d'entrée renvoyés peuvent ne pas inclure toutes les options de ligne de commande.

  3. Si votre programme lit l'entrée du Standard.instdin d'origine, il sera perdu lors du redémarrage.

  4. Beaucoup de ces astuces et hacks échoueront en présence d'un fichier SecurityManager.

La partie ne devrait pas avoir besoin

Je vous recommande de concevoir votre application de manière à ce qu'il soit facile de tout nettoyer et ensuite de créer une nouvelle instance de votre classe "principale".

De nombreuses applications sont conçues pour ne rien faire d'autre que créer une instance dans la méthode main:

public class MainClass {
    ...
    public static void main(String[] args) {
        new MainClass().launch();
    }
    ...
}

En utilisant ce modèle, il devrait être assez facile de faire quelque chose comme:

public class MainClass {
    ...
    public static void main(String[] args) {
        boolean restart;
        do {
            restart = new MainClass().launch();
        } while (restart);
    }
    ...
}

et laissez launch()return true si et seulement si l'application a été arrêtée de telle sorte qu'elle doit être redémarrée.


3
+1 pour de meilleurs conseils de conception; même si, parfois, ce n'est tout simplement pas possible, surtout si vous utilisez JNI, par exemple.
maerics

Eh bien, une bibliothèque native pourrait modifier l'état global qui ne peut pas être modifié depuis l'interface JNI, il n'y aurait donc aucun moyen de «redémarrer» l'état du programme autrement qu'en redémarrant le processus. Bien sûr, la bibliothèque native devrait être mieux conçue, mais parfois vous dépendez de choses que vous ne pouvez pas contrôler.
maerics

Ok, mais avec ce raisonnement, vous pouvez tout aussi bien avoir une pure bibliothèque Java modifiant certaines variables statiques internes. Ce serait cependant un défaut de conception et ne devrait pas se produire dans des bibliothèques bien écrites.
aioobe

1
Votre réponse est incorrecte, car cela est parfaitement possible même sans applications / démons externes comme le montrent Meinersbur et ma propre réponse. Et à des fins de mise à jour automatique, le redémarrage d'une application est une bonne solution, il est donc également nécessaire de redémarrer les applications.
Veger

1
Mais vous faites utiliser une application externe: java! Vous oubliez que Java est une spécification de langage, pas un programme. Et si j'exécute votre programme en utilisant un autre jvm, comme kaffe par exemple? Mis à jour ma réponse de toute façon :-)
aioobe

9

À proprement parler, un programme Java ne peut pas redémarrer lui-même car pour ce faire, il doit tuer la JVM dans laquelle il est en cours d'exécution, puis la redémarrer, mais une fois que la JVM n'est plus en cours d'exécution (tuée), aucune action ne peut être entreprise.

Vous pouvez faire quelques astuces avec des chargeurs de classes personnalisés pour charger, emballer et redémarrer les composants AWT, mais cela causera probablement beaucoup de maux de tête en ce qui concerne la boucle d'événements de l'interface graphique.

En fonction de la façon dont l'application est lancée, vous pouvez démarrer la JVM dans un script wrapper qui contient une boucle do / while, qui se poursuit pendant que la JVM se ferme avec un code particulier, puis l'application AWT devra appeler System.exit(RESTART_CODE). Par exemple, dans le pseudocode de script:

DO
  # Launch the awt program
  EXIT_CODE = # Get the exit code of the last process
WHILE (EXIT_CODE == RESTART_CODE)

L'application AWT doit quitter la JVM avec autre chose que RESTART_CODE lors d'une terminaison "normale" qui ne nécessite pas de redémarrage.


solution très intéressante. Le problème sur OSX est que, généralement, les applications Java sont exécutées à partir d'un fichier compilé JavaApplicationStub... Je ne sais pas s'il existe un moyen facile de contourner cela.
Dan Rosenstark

7

Eclipse redémarre généralement après l'installation d'un plugin. Ils le font à l'aide d'un wrapper eclipse.exe (application de lancement) pour Windows. Cette application exécute le jar principal de l'exécuteur eclipse et si l'application java eclipse se termine par un code de relance, eclipse.exe redémarre l'atelier. Vous pouvez créer un morceau similaire de code natif, de script shell ou d'un autre wrapper de code java pour effectuer le redémarrage.


5

les fenêtres

public void restartApp(){

    // This launches a new instance of application dirctly, 
    // remember to add some sleep to the start of the cmd file to make sure current instance is
    // completely terminated, otherwise 2 instances of the application can overlap causing strange
    // things:)

    new ProcessBuilder("cmd","/c start /min c:/path/to/script/that/launches/my/application.cmd ^& exit").start();
    System.exit(0);
}

/ min pour démarrer le script dans la fenêtre réduite

^ & quitter pour fermer la fenêtre cmd après avoir terminé

un exemple de script cmd pourrait être

@echo off
rem add some sleep (e.g. 10 seconds) to allow the preceding application instance to release any open resources (like ports) and exit gracefully, otherwise the new instance could fail to start
sleep 10   
set path=C:\someFolder\application_lib\libs;%path%
java -jar application.jar

dormir 10 dormir pendant 10 secondes



4

Bien que cette question soit ancienne et répondue, je suis tombé sur un problème avec certaines des solutions et j'ai décidé d'ajouter ma suggestion dans le mélange.

Le problème avec certaines des solutions est qu'elles créent une seule chaîne de commande. Cela crée des problèmes lorsque certains paramètres contiennent des espaces, en particulier java.home .

Par exemple, sur Windows, la ligne

final String javaBin = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";

Peut renvoyer quelque chose comme ceci:C:\Program Files\Java\jre7\bin\java

Cette chaîne doit être placée entre guillemets ou échappée en raison de l'espace dans Program Files. Pas un gros problème, mais quelque peu ennuyeux et sujet aux erreurs, en particulier dans les applications multiplateformes.

Par conséquent, ma solution construit la commande comme un tableau de commandes:

public static void restart(String[] args) {

        ArrayList<String> commands = new ArrayList<String>(4 + jvmArgs.size() + args.length);
        List<String> jvmArgs = ManagementFactory.getRuntimeMXBean().getInputArguments();

        // Java
        commands.add(System.getProperty("java.home") + File.separator + "bin" + File.separator + "java");

        // Jvm arguments
        for (String jvmArg : jvmArgs) {
            commands.add(jvmArg);
        }

        // Classpath
        commands.add("-cp");
        commands.add(ManagementFactory.getRuntimeMXBean().getClassPath());

        // Class to be executed
        commands.add(BGAgent.class.getName());

        // Command line arguments
        for (String arg : args) {
            commands.add(arg);
        }

        File workingDir = null; // Null working dir means that the child uses the same working directory

        String[] env = null; // Null env means that the child uses the same environment

        String[] commandArray = new String[commands.size()];
        commandArray = commands.toArray(commandArray);

        try {
            Runtime.getRuntime().exec(commandArray, env, workingDir);
            System.exit(0);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

3

Je faisais moi-même des recherches sur le sujet lorsque je suis tombé sur cette question.

Indépendamment du fait que la réponse est déjà acceptée, je voudrais toujours proposer une approche alternative pour l'exhaustivité. Plus précisément, Apache Ant a servi de solution très flexible.

Fondamentalement, tout se résume à un fichier de script Ant avec une seule tâche d'exécution Java (voir ici et ici ) appelée à partir d'un code Java (voir ici ). Ce code Java, qui peut être un lancement de méthode , pourrait faire partie de l'application qui doit être redémarrée. L'application doit avoir une dépendance sur la bibliothèque Apache Ant (jar).

Chaque fois que l'application doit être redémarrée, elle doit appeler le lancement de méthode et quitter la machine virtuelle. La tâche Ant java doit avoir les options fork et spawn définies sur true.

Voici un exemple de script Ant:

<project name="applaucher" default="launch" basedir=".">
<target name="launch">
    <java classname="package.MasinClass" fork="true" spawn="true">
        <jvmarg value="-splash:splash.jpg"/>
        <jvmarg value="-D other VM params"/>
        <classpath>
            <pathelement location="lib-1.jar" />
            ...
            <pathelement location="lib-n.jar" />
        </classpath>
    </java>
</target>
</project>

Le code de la méthode de lancement peut ressembler à ceci:

public final void launch(final String antScriptFile) {
 /* configure Ant and execute the task */
   final File buildFile = new File(antScriptFile);
   final Project p = new Project();
   p.setUserProperty("ant.file", buildFile.getAbsolutePath());

   final DefaultLogger consoleLogger = new DefaultLogger();
   consoleLogger.setErrorPrintStream(System.err);
   consoleLogger.setOutputPrintStream(System.out);
   consoleLogger.setMessageOutputLevel(Project.MSG_INFO);
   p.addBuildListener(consoleLogger);

   try {
       p.fireBuildStarted();
       p.init();
       final ProjectHelper helper = ProjectHelper.getProjectHelper();
       p.addReference("ant.projectHelper", helper);
       helper.parse(p, buildFile);
       p.executeTarget(p.getDefaultTarget());
       p.fireBuildFinished(null);
   } catch (final BuildException e) {
       p.fireBuildFinished(e);
   }

   /* exit the current VM */
   System.exit(0);

}

Une chose très pratique ici est que le même script est utilisé pour le démarrage initial de l'application ainsi que pour les redémarrages.


3

Il suffit d'ajouter des informations qui ne sont pas présentes dans d'autres réponses.

Si procfs /proc/self/cmdline est disponible

Si vous exécutez dans un environnement qui fournit procfs et que le /procsystème de fichiers est donc disponible (ce qui signifie que ce n'est pas une solution portable), vous pouvez faire lire Java /proc/self/cmdlineafin de redémarrer lui-même, comme ceci:

public static void restart() throws IOException {
    new ProcessBuilder(getMyOwnCmdLine()).inheritIO().start();
}
public static String[] getMyOwnCmdLine() throws IOException {
    return readFirstLine("/proc/self/cmdline").split("\u0000");
}
public static String readFirstLine(final String filename) throws IOException {
    try (final BufferedReader in = new BufferedReader(new FileReader(filename))) {
        return in.readLine();
    }
}

Sur les systèmes avec /proc/self/cmdlinedisponibles, c'est probablement la manière la plus élégante de "redémarrer" le processus Java actuel à partir de Java. Aucun JNI impliqué, et aucune estimation des chemins et des choses requises. Cela prendra également en charge toutes les options JVM transmises au javabinaire. La ligne de commande sera exactement identique à celle du processus JVM actuel.

De nombreux systèmes UNIX, y compris GNU / Linux (y compris Android) ont de nos jours des procfs. Cependant, sur certains comme FreeBSD, il est obsolète et est en cours de suppression. Mac OS X est une exception dans le sens où il n'a pas de procfs . Windows n'a pas non plus procfs . Cygwin a procfs mais il est invisible pour Java car il n'est visible que pour les applications utilisant les DLL Cygwin au lieu des appels système Windows, et Java ne connaît pas Cygwin.

N'oubliez pas d'utiliser ProcessBuilder.inheritIO()

La valeur par défaut est que stdin/ stdout/ stderr(en Java appelé System.in/ System.out/ System.err) du processus démarré est défini sur des canaux qui permettent au processus en cours d'exécution de communiquer avec le processus nouvellement démarré. Si vous souhaitez redémarrer le processus en cours, ce n'est probablement pas ce que vous souhaitez . Au lieu de cela, vous voudriez que stdin/ stdout/ stderrsoient les mêmes que ceux de la VM actuelle. C'est ce qu'on appelle hérité . Vous pouvez le faire en appelant inheritIO()votre ProcessBuilderinstance.

Pitfall sur Windows

Un cas d'utilisation fréquent d'une restart()fonction est de redémarrer l'application après une mise à jour. La dernière fois que j'ai essayé cela sous Windows, c'était problématique. Lorsqu'elle a remplacé le .jarfichier de l'application par la nouvelle version, l'application a commencé à se comporter mal et à donner des exceptions au .jarfichier. Je dis juste, au cas où ce serait votre cas d'utilisation. À l'époque, j'ai résolu le problème en enveloppant l'application dans un fichier de commandes et en utilisant une valeur de retour magique à partir de System.exit()laquelle j'ai interrogé dans le fichier de commandes et que le fichier de commandes redémarre l'application à la place.


2

Ancienne question et tout ça. Mais c'est encore une autre façon qui offre certains avantages.

Sous Windows, vous pouvez demander au planificateur de tâches de redémarrer votre application pour vous. Cela présente l'avantage d'attendre un laps de temps spécifique avant de redémarrer l'application. Vous pouvez accéder au gestionnaire de tâches et supprimer la tâche et elle cesse de se répéter.

SimpleDateFormat hhmm = new SimpleDateFormat("kk:mm");    
Calendar aCal = Calendar.getInstance(); 
aCal.add(Calendar.SECOND, 65);
String nextMinute = hhmm.format(aCal.getTime()); //Task Scheduler Doesn't accept seconds and won't do current minute.
String[] create = {"c:\\windows\\system32\\schtasks.exe", "/CREATE", "/F", "/TN", "RestartMyProg", "/SC", "ONCE", "/ST", nextMinute, "/TR", "java -jar c:\\my\\dev\\RestartTest.jar"};  
Process proc = Runtime.getRuntime().exec(create, null, null);
System.out.println("Exit Now");
try {Thread.sleep(1000);} catch (Exception e){} // just so you can see it better
System.exit(0);

2

Similaire à la réponse ` ` améliorée ' ' de Yoda , mais avec de nouvelles améliorations (à la fois fonctionnelles, lisibles et testables). Il est maintenant sûr de s'exécuter et redémarre autant de fois que le nombre d'arguments du programme donné.

  • Aucune accumulation d' JAVA_TOOL_OPTIONSoptions.
  • Recherche automatiquement la classe principale.
  • Hérite du stdout / stderr actuel.

public static void main(String[] args) throws Exception {
    if (args.length == 0)
        return;
    else
        args = Arrays.copyOf(args, args.length - 1);

    List<String> command = new ArrayList<>(32);
    appendJavaExecutable(command);
    appendVMArgs(command);
    appendClassPath(command);
    appendEntryPoint(command);
    appendArgs(command, args);

    System.out.println(command);
    try {
        new ProcessBuilder(command).inheritIO().start();
    } catch (IOException ex) {
        ex.printStackTrace();
    }
}

private static void appendJavaExecutable(List<String> cmd) {
    cmd.add(System.getProperty("java.home") + File.separator + "bin" + File.separator + "java");
}

private static void appendVMArgs(Collection<String> cmd) {
    Collection<String> vmArguments = ManagementFactory.getRuntimeMXBean().getInputArguments();

    String javaToolOptions = System.getenv("JAVA_TOOL_OPTIONS");
    if (javaToolOptions != null) {
        Collection<String> javaToolOptionsList = Arrays.asList(javaToolOptions.split(" "));
        vmArguments = new ArrayList<>(vmArguments);
        vmArguments.removeAll(javaToolOptionsList);
    }

    cmd.addAll(vmArguments);
}

private static void appendClassPath(List<String> cmd) {
    cmd.add("-cp");
    cmd.add(ManagementFactory.getRuntimeMXBean().getClassPath());
}

    private static void appendEntryPoint(List<String> cmd) {
    StackTraceElement[] stackTrace          = new Throwable().getStackTrace();
    StackTraceElement   stackTraceElement   = stackTrace[stackTrace.length - 1];
    String              fullyQualifiedClass = stackTraceElement.getClassName();
    String              entryMethod         = stackTraceElement.getMethodName();
    if (!entryMethod.equals("main"))
        throw new AssertionError("Entry point is not a 'main()': " + fullyQualifiedClass + '.' + entryMethod);

    cmd.add(fullyQualifiedClass);
}

private static void appendArgs(List<String> cmd, String[] args) {
    cmd.addAll(Arrays.asList(args));
}

V1.1 Bugfix: pointeur nul si JAVA_TOOL_OPTIONS n'est pas défini


Exemple:

$ java -cp Temp.jar Temp a b c d e
[/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java, -cp, Temp.jar, Temp, a, b, c, d]
[/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java, -cp, Temp.jar, Temp, a, b, c]
[/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java, -cp, Temp.jar, Temp, a, b]
[/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java, -cp, Temp.jar, Temp, a]
[/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java, -cp, Temp.jar, Temp]
$

-13
System.err.println("Someone is Restarting me...");
setVisible(false);
try {
    Thread.sleep(600);
} catch (InterruptedException e1) {
    e1.printStackTrace();
}
setVisible(true);

Je suppose que vous ne voulez pas vraiment arrêter l'application, mais la "redémarrer". Pour cela, vous pouvez utiliser ceci et ajouter votre "Reset" avant le sommeil et après la fenêtre invisible.


4
L'utilisateur a demandé de redémarrer l'application non seulement de masquer et d'afficher une fenêtre.
Amr Lotfy
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.