001// SPDX-License-Identifier: GPL-3.0-or-later
002
003package es.uvigo.esei.sing.textproc;
004
005import java.io.FileInputStream;
006import java.io.IOException;
007import java.io.InputStream;
008import java.time.Duration;
009import java.util.HashSet;
010import java.util.Set;
011import java.util.logging.Level;
012import java.util.logging.Logger;
013
014import com.github.lalyos.jfiglet.FigletFont;
015
016import es.uvigo.esei.sing.textproc.logging.TextProcLogging;
017import es.uvigo.esei.sing.textproc.persistence.TextProcPersistence;
018import es.uvigo.esei.sing.textproc.process.ProcessingProcess;
019import es.uvigo.esei.sing.textproc.step.ProcessingStepService;
020import es.uvigo.esei.sing.textproc.step.ProcessingStepServices;
021import lombok.AccessLevel;
022import lombok.Getter;
023import lombok.NoArgsConstructor;
024import lombok.NonNull;
025
026/**
027 * Class whose responsibility is the high level application life cycle
028 * management. It also provides references to objects and data which should be
029 * shared between parts of the application.
030 *
031 * @author Alejandro González García
032 * @implNote The implementation of this class is thread-safe.
033 */
034@NoArgsConstructor(access = AccessLevel.PRIVATE)
035public final class TextProc {
036        /**
037         * The name of the application.
038         */
039        public static final String APP_NAME = TextProc.class.getSimpleName();
040
041        private static final TextProc APP_INSTANCE = new TextProc();
042
043        @Getter(AccessLevel.PRIVATE)
044        private final Logger appLogger;
045
046        {
047                TextProcLogging.initialize(APP_NAME);
048                this.appLogger = TextProcLogging.getLogger();
049        }
050
051        /**
052         * Entry point of the application. It should be called only by the JVM.
053         *
054         * @param args The command line parameters passed to the application.
055         */
056        public static void main(final String[] args) {
057                System.exit(get().run(args));
058        }
059
060        /**
061         * Returns the only instance of this class in the JVM.
062         *
063         * @return The described instance.
064         */
065        public static TextProc get() {
066                return APP_INSTANCE;
067        }
068
069        /**
070         * Prints progress messages to the standard output stream.
071         *
072         * @param message The message to print.
073         * @throws IllegalArgumentException If {@code message} is {@code null}.
074         */
075        public void printProgressMessage(@NonNull final String message) {
076                System.out.print("> ");
077                System.out.println(message);
078        }
079
080        /**
081         * Entry point of the application, executed within the context of the only
082         * instance of this class in the JVM.
083         *
084         * @param args The command line parameters passed to the application.
085         * @return The exit status of the application process.
086         */
087        private int run(final String[] args) {
088                final TextProcPersistence persistence = TextProcPersistence.get();
089                InputStream processInputStream;
090                int exitStatus = 0;
091                final Set<Class<?>> processedDocumentTypes = new HashSet<>();
092
093                if (args.length > 1) {
094                        throw new IllegalArgumentException("Unexpected number of arguments");
095                }
096
097                printStartBanners();
098
099                try {
100                        printProgressMessage("Initializing Java Persistence API entity management...");
101
102                        // Get all the processed document types available
103                        for (final ProcessingStepService processingStepService : ProcessingStepServices.getServiceLoader()) {
104                                processedDocumentTypes.addAll(processingStepService.getProcessedDocumentTypes());
105                        }
106                        persistence.start(processedDocumentTypes);
107
108                        printProgressMessage("JPA entity management factory initialized.");
109
110                        if (args.length == 0 || (args.length == 1 && args[0] == "-")) {
111                                // Read the process from standard input
112                                processInputStream = System.in;
113                        } else {
114                                // One argument. Read the process from a file
115                                processInputStream = new FileInputStream(args[0]);
116                        }
117
118                        printProgressMessage(
119                                "Executing process definined in " + (processInputStream == System.in ? "standard input" : args[0]) + "."
120                        );
121
122                        // Do the actual work
123                        final long startTimestamp = System.currentTimeMillis();
124                        new ProcessingProcess().executeProcessDeclaration(processInputStream);
125                        final long endTimestamp = System.currentTimeMillis();
126
127                        final Duration elapsedTime = Duration.ofMillis(endTimestamp - startTimestamp);
128                        final String elapsedTimeString = String.format(
129                                "%d day/s, %02d:%02d:%02d", elapsedTime.toDaysPart(),
130                                elapsedTime.toHoursPart(), elapsedTime.toMinutesPart(), elapsedTime.toSecondsPart()
131                        );
132
133                        System.out.println();
134                        System.out.println();
135
136                        printProgressMessage(
137                                "Process done (" + elapsedTimeString + "). " +
138                                "Consider optimizing the processed entity tables for optimal performance. Have a nice day!"
139                        );
140                } catch (final Throwable exc) {
141                        getAppLogger().log(
142                                Level.SEVERE, "An exception has occurred. Aborting program execution.", exc
143                        );
144
145                        exitStatus = Math.abs(exc.hashCode()) % 255 + 1; // Make sure it's greater than 0
146                } finally {
147                        printProgressMessage("Cleaning up before stopping...");
148
149                        persistence.stop();
150
151                        printProgressMessage("Cleanup done.");
152                }
153
154                return exitStatus;
155        }
156
157        /**
158         * Prints startup messages to the standard output stream.
159         */
160        private void printStartBanners() {
161                // Application name
162                try {
163                        System.out.println(FigletFont.convertOneLine(APP_NAME));
164                } catch (final IOException exc) {
165                        // Fallback to not so pretty banner
166                        System.out.println(APP_NAME);
167                        System.out.println();
168                }
169
170                // License info
171                System.out.println(
172                        "Copyright (C) 2020\n" + 
173                        "This program comes with ABSOLUTELY NO WARRANTY.\n" + 
174                        "This is free software, and you are welcome to redistribute it\n" + 
175                        "under certain conditions. See LICENSE for details."
176                );
177                System.out.println();
178        }
179}