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}