001/* 002 * ============================================================================ 003 * Copyright © 2015 Square, Inc. 004 * Copyright for the modifications © 2018-2024 by Thomas Thrien. 005 * ============================================================================ 006 * 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 */ 019 020package org.tquadrat.foundation.javacomposer.internal; 021 022import static java.lang.Character.isISOControl; 023import static java.lang.String.format; 024import static java.lang.Thread.currentThread; 025import static org.apiguardian.api.API.Status.INTERNAL; 026import static org.tquadrat.foundation.lang.Objects.checkState; 027import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 028 029import javax.lang.model.element.Modifier; 030import java.util.Arrays; 031import java.util.LinkedHashSet; 032import java.util.Optional; 033import java.util.Set; 034 035import org.apiguardian.api.API; 036import org.tquadrat.foundation.annotation.ClassVersion; 037import org.tquadrat.foundation.annotation.UtilityClass; 038import org.tquadrat.foundation.exception.PrivateConstructorForStaticClassCalledError; 039import org.tquadrat.foundation.exception.ValidationException; 040 041/** 042 * Several utility functions to be used with JavaComposer. 043 * 044 * @author Square,Inc. 045 * @modified Thomas Thrien - thomas.thrien@tquadrat.org 046 * @version $Id: Util.java 1085 2024-01-05 16:23:28Z tquadrat $ 047 * @since 0.0.5 048 * 049 * @UMLGraph.link 050 */ 051@SuppressWarnings( "NewClassNamingConvention" ) 052@UtilityClass 053@ClassVersion( sourceVersion = "$Id: Util.java 1085 2024-01-05 16:23:28Z tquadrat $" ) 054public final class Util 055{ 056 /*-----------*\ 057 ====** Constants **======================================================== 058 \*-----------*/ 059 /** 060 * The placeholder for {@code null} references. 061 */ 062 @API( status = INTERNAL, since = "0.0.5" ) 063 public static final Object NULL_REFERENCE = new Object(); 064 065 /** 066 * The return value of 067 * {@link #createDebugOutput(boolean)} 068 * when no debug output is desired. 069 */ 070 @SuppressWarnings( "OptionalUsedAsFieldOrParameterType" ) 071 @API( status = INTERNAL, since = "0.0.5" ) 072 public static final Optional<DebugOutput> NO_DEBUG_OUTPUT = Optional.empty(); 073 074 /*--------------*\ 075 ====** Constructors **===================================================== 076 \*--------------*/ 077 /** 078 * No instance allowed for this class. 079 */ 080 private Util() { throw new PrivateConstructorForStaticClassCalledError( Util.class ); } 081 082 /*---------*\ 083 ====** Methods **========================================================== 084 \*---------*/ 085 /** 086 * Translates the given character into a String; when that character is a 087 * special character, it will be escaped properly so that it can be used 088 * in a Java String literal. 089 * 090 * @param c The input character. 091 * @return The target String. 092 * 093 * @see <a href="https://docs.oracle.com/javase/specs/jls/se10/html/jls-3.html#jls-3.10.6">The Java Language Specification: 3.10.6. Escape Sequences for Character and String Literals </a> 094 */ 095 @API( status = INTERNAL, since = "0.0.5" ) 096 public static final String characterLiteralWithoutSingleQuotes( final char c ) 097 { 098 final var retValue = switch( c ) 099 { 100 case '\b' -> "\\b"; // u0008: backspace (BS) 101 case '\t' -> "\\t"; // u0009: horizontal tab (HT) 102 case '\n' -> "\\n"; // u000a: linefeed (LF) 103 case '\f' -> "\\f"; // u000c: form feed (FF) 104 case '\r' -> "\\r"; // u000d: carriage return (CR) 105 case '"' -> Character.toString( c ); // u0022: double quote (") 106 case '\'' -> "\\'"; // u0027: single quote (') 107 case '\\' -> "\\\\"; // u005c: backslash (\) 108 default -> isISOControl( c ) ? format( "\\u%04x", (int) c ) : Character.toString( c ); 109 }; 110 111 //---* Done *---------------------------------------------------------- 112 return retValue; 113 } // characterLiteralWithoutSingleQuotes() 114 115 /** 116 * Creates the debug output. 117 * 118 * @param addDebugOutput {@code true} if some debug output should be 119 * added to the generated code, {@code false} otherwise. 120 * @return An instance of 121 * {@link Optional} 122 * that holds the debug output; empty if the parameter 123 * {@code addDebugOutput} is {@code false}. 124 * 125 * @see #NO_DEBUG_OUTPUT 126 */ 127 @API( status = INTERNAL, since = "0.0.5" ) 128 public static final Optional<DebugOutput> createDebugOutput( final boolean addDebugOutput ) 129 { 130 //---* Get the caller's caller *--------------------------------------- 131 final var retValue = addDebugOutput ? Optional.of( new DebugOutput( findCaller() ) ) : NO_DEBUG_OUTPUT; 132 133 //---* Done *---------------------------------------------------------- 134 return retValue; 135 } // createDebugOutput() 136 137 /** 138 * <p>{@summary This method will find the method that makes the call into 139 * the Java Composer API and returns the appropriate stack trace 140 * element.}</p> 141 * <p>The respective method is determined by the package name of the 142 * containing class: it does <i>not</i> start with 143 * {@code org.tquadrat.foundation.javacomposer}.</p> 144 * 145 * @return An instance of 146 * {@link Optional} 147 * that holds the stack trace element for the caller; will be empty if 148 * the call was internal. 149 */ 150 @API( status = INTERNAL, since = "0.2.0" ) 151 private static final Optional<StackTraceElement> findCaller() 152 { 153 //---* Retrieve the stack *-------------------------------------------- 154 final var stackTraceElements = currentThread().getStackTrace(); 155 final var len = stackTraceElements.length; 156 157 //---* Search the stack *---------------------------------------------- 158 Optional<StackTraceElement> retValue = Optional.empty(); 159 FindLoop: for( var i = 1; i < len; ++i ) 160 { 161 final var className = stackTraceElements [i].getClassName(); 162 if( !className.startsWith( "org.tquadrat.foundation.javacomposer" ) ) 163 { 164 retValue = Optional.of( stackTraceElements [i] ); 165 break FindLoop; 166 } 167 } // FindLoop: 168 169 //---* Done *---------------------------------------------------------- 170 return retValue; 171 } // findCaller() 172 173 /** 174 * Returns the Java String literal representing {@code value}, including 175 * escaping double quotes. 176 * 177 * @param value The input String. 178 * @param indent The indentation String that has to be added in case of 179 * a line break. 180 * @return The Java literal. 181 */ 182 @API( status = INTERNAL, since = "0.0.5" ) 183 public static String stringLiteralWithDoubleQuotes( final String value, final String indent ) 184 { 185 final var retValue = new StringBuilder( value.length() + 2 ); 186 retValue.append( '"' ); 187 ScanLoop: for( var i = 0; i < value.length(); ++i ) 188 { 189 final var currentChar = value.charAt( i ); 190 191 //---* The trivial case: single quote must not be escaped *-------- 192 if( currentChar == '\'' ) 193 { 194 retValue.append( "'" ); 195 continue ScanLoop; 196 } 197 198 //---* Another trivial case: double quotes must be escaped *------- 199 if( currentChar == '"' ) 200 { 201 retValue.append( '\\' ).append( '"' ); 202 continue ScanLoop; 203 } 204 205 /* 206 * The default case: just let characterLiteralWithoutSingleQuotes() 207 * do its work. 208 */ 209 retValue.append( characterLiteralWithoutSingleQuotes( currentChar ) ); 210 211 //---* Do we need to append indent after linefeed? *--------------- 212 if( currentChar == '\n' && i + 1 < value.length() ) 213 { 214 /* 215 * Originally, the indentation string was appended twice. 216 */ 217 retValue.append( "\"\n" ).append( indent ).append( "+ \"" ); 218 } 219 } // ScanLoop: 220 retValue.append( '"' ); 221 222 //---* Done *---------------------------------------------------------- 223 return retValue.toString(); 224 } // stringLiteralWithDoubleQuotes() 225 226 /** 227 * Checks whether the given 228 * {@link Set} 229 * of 230 * {@link Modifier} 231 * instances does contain one and only one of the {@code Modifier} 232 * instances given with the second argument, {@code mutuallyExclusive}. 233 * 234 * @param modifiers The set to check. 235 * @param mutuallyExclusive A list of values from which one and only 236 * one must be in the {@code modifiers} set. 237 * @throws ValidationException None or more than one {@code Modifier} 238 * instance was found. 239 */ 240 @API( status = INTERNAL, since = "0.0.5" ) 241 public static final void requireExactlyOneOf( final Set<Modifier> modifiers, final Modifier... mutuallyExclusive ) throws ValidationException 242 { 243 requireNonNullArgument( modifiers, "modifiers" ); 244 final var count = (int) Arrays.stream( requireNonNullArgument( mutuallyExclusive, "mutuallyExclusive" ) ) 245 .filter( modifiers::contains ) 246 .count(); 247 checkState( count == 1, () -> new ValidationException( "modifiers %s must contain one of %s".formatted( modifiers, Arrays.toString( mutuallyExclusive ) ) ) ); 248 } // requireExactlyOneOf() 249 250 /** 251 * Creates a new set with the combined contents of the given sets. 252 * 253 * @param <T> The type of the set elements. 254 * @param firstSet The first set. 255 * @param secondSet The second set. 256 * @return The combined set. 257 */ 258 @SuppressWarnings( "TypeMayBeWeakened" ) 259 @API( status = INTERNAL, since = "0.0.5" ) 260 public static final <T> Set<T> union( final Set<? extends T> firstSet, final Set<? extends T> secondSet ) 261 { 262 final Set<T> retValue = new LinkedHashSet<>( firstSet ); 263 retValue.addAll( secondSet ); 264 265 //---* Done *---------------------------------------------------------- 266 return retValue; 267 } // union() 268} 269// class Util 270 271/* 272 * End of File 273 */