001/*
002 * ============================================================================
003 * Copyright © 2002-2024 by Thomas Thrien.
004 * All Rights Reserved.
005 * ============================================================================
006 *
007 * Licensed to the public under the agreements of the GNU Lesser General Public
008 * License, version 3.0 (the "License"). You may obtain a copy of the License at
009 *
010 *      http://www.gnu.org/licenses/lgpl.html
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
014 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
015 * License for the specific language governing permissions and limitations
016 * under the License.
017 */
018
019package org.tquadrat.foundation.javacomposer;
020
021import static java.lang.String.format;
022import static java.util.Arrays.stream;
023import static java.util.stream.Collectors.joining;
024import static org.apiguardian.api.API.Status.STABLE;
025import static org.tquadrat.foundation.lang.CommonConstants.EMPTY_STRING;
026import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument;
027
028import java.util.Optional;
029import java.util.StringJoiner;
030
031import org.apiguardian.api.API;
032import org.tquadrat.foundation.annotation.ClassVersion;
033
034/**
035 *  <p>{@summary Warnings that can be suppressed with the
036 *  {@link SuppressWarnings &#64;SuppressWarnings}
037 *  annotation.}</p>
038 *  <p>There are different – partially overlapping – sets of these warnings
039 *  and the arguments to the annotation; one is defined by Java directly,
040 *  another one is defined by Eclipse, a third one by IntelliJ&nbsp;IDEA (see
041 *  {@href https://gist.github.com/vegaasen/157fbc6dce8545b7f12c}
042 *  for a – not exhausting – list). Other IDEs and/or Java distributions may
043 *  add more.</p>
044 *
045 *  @see <a href="http://help.eclipse.org/photon/index.jsp?topic=%2Forg.eclipse.jdt.doc.user%2Ftasks%2Ftask-suppress_warnings.htm&cp=1_4_8_4">Eclipse documentation</a>
046 *
047 *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
048 *  @version $Id: SuppressableWarnings.java 1085 2024-01-05 16:23:28Z tquadrat $
049 *  @since 0.0.5
050 *
051 *  @UMLGraph.link
052 */
053@ClassVersion( sourceVersion = "$Id: SuppressableWarnings.java 1085 2024-01-05 16:23:28Z tquadrat $" )
054@API( status = STABLE, since = "0.0.5" )
055public enum SuppressableWarnings
056{
057        /*------------------*\
058    ====** Enum Declaration **=================================================
059        \*------------------*/
060    /**
061     *  All warnings.
062     */
063    ALL( "all" ),
064
065    /**
066     *  An abstract class is not implemented in the current project scope.
067     *  Common for libraries.
068     */
069    ABSTRACT_CLASS_NEVER_IMPLEMENTED( "AbstractClassNeverImplemented" ),
070
071    /**
072     *  A class was qualified as abstract without having any abstract methods.
073     *  Happens sometimes for adapter classes.
074     */
075    ABSTRACT_CLASS_WITHOUT_ABSTRACT_METHODS( "AbstractClassWithoutAbstractMethods" ),
076
077    /**
078     *  A method accesses a non-public (private) field of another object.
079     */
080    ACCESSING_NON_PUBLIC_FIELD_OF_ANOTHER_OBJECT( "AccessingNonPublicFieldOfAnotherObject" ),
081
082    /**
083     *  A type variable bound is more restrictive than necessary; sometimes,
084     *  this is wrong, in particular when the bound type is an {@code enum}.
085     */
086    BOUNDED_WILDCARD( "BoundedWildcard" ),
087
088    /**
089     *  Boxing/unboxing is used implicitly.
090     */
091    BOXING( "boxing" ),
092
093    /**
094     *  <p>{@summary Some possible cases in a {@code switch} statement have
095     *  been omitted purposely.}</p>
096     *  <p>This suppression is specific to Eclipse and works only as a comment,
097     *  like this:</p>
098     *  <pre><code>  …
099     *  //$CASES-OMITTED$
100     *  switch( … )
101     *  {
102     *      case …
103     *      default …
104     *  }</code></pre>
105     *  <p>It cannot be used with
106     *  {@link #createSuppressWarningsAnnotation(JavaComposer,SuppressableWarnings...)}
107     *  nor with
108     *  {@link #createSuppressWarningsCommentForIDEA(JavaComposer,SuppressableWarnings...)}.</p>
109     */
110    CASES_OMITTED( "$CASES-OMITTED$" ),
111
112    /**
113     *  An unnecessary cast is used.
114     */
115    CAST( "cast" ),
116
117    /**
118     *  An object is cast to a concrete class, instead to one of the interfaces
119     *  implemented by that class.
120     */
121    CAST_TO_CONCRETE_CLASS( "CastToConcreteClass" ),
122
123    /**
124     *  <p>{@summary A class or interface references one of its own
125     *  subclasses/implementations.} This happens for example in factory
126     *  methods in the interface or base class.</p>
127     *  <p>Although potentially dangerous, it can be done safely if interface
128     *  or baseclass are sealed.</p>
129     */
130    CLASS_REFERENCES_SUBCLASS( "ClassReferencesSubclass" ),
131
132    /**
133     *  The current class has too many fields according to the configured
134     *  metric value.
135     */
136    CLASS_WITH_TOO_MANY_FIELDS( "ClassWithTooManyFields" ),
137
138    /**
139     *  The current class or interface has too many methods according to the
140     *  configured metric value.
141     */
142    CLASS_WITH_TOO_MANY_METHODS( "ClassWithTooManyMethods" ),
143
144    /**
145     *  A field or parameter is declared with a concrete collection class (like
146     *  {@link java.util.HashSet HashSet})
147     *  instead of the respective Interface
148     *  ({@link java.util.Set Set}
149     *  in this case).
150     */
151    COLLECTION_DECLARED_AS_CONCRETE_CLASS( "CollectionDeclaredAsConcreteClass" ),
152
153    /**
154     *  A necessary
155     *  {@link Deprecated &#64;Deprecated}
156     *  annotation is missing.
157     *
158     *  @note   To suppress this warning makes only sense on the class level
159     *      when there are lots of deprecated methods in it, missing the
160     *      {@code @Deprecated} annotation.
161     */
162    DEPRECATION_ANNOTATION( "dep-ann" ),
163
164    /**
165     *  A deprecated element is used.
166     */
167    DEPRECATION( "deprecation" ),
168
169    /**
170     *  A (private) attribute can be moved to be a local variable.
171     */
172    FIELD_CAN_BE_LOCAL( "FieldCanBeLocal" ),
173
174    /**
175     *  A private attribute of a class is never changed.
176     */
177    FIELD_MAY_BE_FINAL( "FieldMayBeFinal" ),
178
179    /**
180     *  <p>{@summary A component in a {@code for} loop was omitted.}</p>
181     *  <p>The usual {@code for} loop looks like this:</p>
182     *  <pre><code>  …
183     *  for( var i = 0; i &lt; max; ++i )
184     *  {
185     *      …
186     *  }
187     *  …</code></pre>
188     *  <p>But when using an instance of
189     *  {@link java.util.Iterator},
190     *  a common pattern looks like below:</p>
191     *  <pre><code>  …
192     *  Collection&lt;T&gt; c = …
193     *  for( var i = c.iterator(); i.hasNext(); )
194     *  {
195     *     var t = i.next();
196     *     …
197     *  }
198     *  …</code></pre>
199     *  <p>The update for {@code i} is missing the header for the loop, and the
200     *  compiler warn about that (or the IDE, whoever  …).</p>
201     */
202    FOR_LOOP_WITH_MISSING_COMPONENT( "ForLoopWithMissingComponent" ),
203
204    /**
205     *  A switch statement is incomplete.
206     */
207    INCOMPLETE_SWITCH( "incomplete-switch" ),
208
209    /**
210     *  The particular class is an inner class or interface of an interface.
211     */
212    INNER_CLASS_OF_INTERFACE( "InnerClassOfInterface" ),
213
214    /**
215     *  The attribute's type is of a concrete class instead of the interface
216     *  that is implemented by that class.
217     */
218    INSTANCE_VARIABLE_OF_CONCRETE_CLASS( "InstanceVariableOfConcreteClass" ),
219
220    /**
221     *  An interface with just one non-default, non-static method syntactically
222     *  can be used as a functional interface, but it does not make sense for
223     *  all of these interfaces semantically.
224     */
225    INTERFACE_MAY_BE_ANNOTATED_FUNCTIONAL( "InterfaceMayBeAnnotatedFunctional" ),
226
227    /**
228     *  A required Javadoc comment is missing or the Javadoc is incomplete or
229     *  invalid.
230     */
231    JAVADOC( "javadoc" ),
232
233    /**
234     *  A &quot;Magic&quot; character is used, instead of a constant or an
235     *  enum.
236     */
237    MAGIC_CHARACTER( "MagicCharacter" ),
238
239    /**
240     *  A &quot;Magic&quot; number is used, instead of a constant.
241     */
242    MAGIC_NUMBER( "MagicNumber" ),
243
244    /**
245     *  The implementation of a  method (like
246     *  {@link Object#clone()}
247     *  for example) does not call the implementation of it in the super class,
248     *  although this is expected.
249     */
250    METHOD_DOESNT_CALL_SUPER_METHOD( "MethodDoesntCallSuperMethod" ),
251
252    /**
253     *  JUnit assert statements, like {@code assertEquals()} do expect their
254     *  arguments in a specific order, usually is the expected value the first
255     *  argument, and the result from the test run is the second argument.
256     */
257    MISORDERED_ASSERT_EQUALS_ARGUMENTS( "MisorderedAssertEqualsArguments" ),
258
259    /**
260     *  Usually, a method that does nothing in meant to be overridden in an
261     *  implementing class. If such a method is in an abstract class, it
262     *  should be abstract, too. But for adapter classes, it is intended that
263     *  the method is no-op and not abstract.
264     */
265    NOOP_METHOD_IN_ABSTRACT_CLASS( "NoopMethodInAbstractClass" ),
266
267    /**
268     *  <p>{@summary There is a call to
269     *  {@link Optional#get()}
270     *  without a previous check whether the {@code Optional} is not empty by
271     *  calling
272     *  {@link Optional#isPresent()}
273     *  or
274     *  {@link Optional#isEmpty()}.}</p>
275     *  <p>This can be valid if it is well known otherwise that the
276     *  {@code Optional} is not empty.</p>
277     */
278    OPTIONAL_GET_WITHOUT_IS_PRESENT( "OptionalGetWithoutIsPresent" ),
279
280    /**
281     *  The current class is too complex (cyclomatic complexity), according to
282     *  the configured metric.
283     */
284    OVERLY_COMPLEX_CLASS( "OverlyComplexClass" ),
285
286    /**
287     *  The current class is too closely coupled to other classes, according to
288     *  the configured metric.
289     */
290    OVERLY_COUPLED_CLASS("OverlyCoupledClass" ),
291
292    /**
293     *  A preview feature was used.
294     */
295    PREVIEW( "preview" ),
296
297    /**
298     *  A class implements one or more interfaces, but some of its public
299     *  methods are not exposed via any of those interfaces. This happens is a
300     *  common pattern with modularisation (Jigsaw), where internal classes are
301     *  public to the whole module, but invisible to the users of the module.
302     */
303    PUBLIC_METHOD_NOT_EXPOSED_IN_INTERFACE( "PublicMethodNotExposedInInterface" ),
304
305    /**
306     *  The default (no-argument) constructor does nothing, and therefore it is
307     *  not necessary to write it down explicitly in the code.
308     */
309    REDUNDANT_NO_ARG_CONSTRUCTOR( "RedundantNoArgConstructor" ),
310
311    /**
312     *  The variable's explicit type can be replaced by {@code var}.
313     */
314    REDUNDANT_EXPLICIT_VARIABLE_TYPE( "RedundantExplicitVariableType" ),
315
316    /**
317     *  An
318     *  {@link AutoCloseable}
319     *  was not closed.
320     */
321    RESOURCE( "resource" ),
322
323    /**
324     *  A method is called with always the same argument. Usually this means
325     *  that the argument is obsolete.
326     */
327    SAME_PARAMETER_VALUE( "SameParameterValue" ),
328
329    /**
330     *  The {@code SerialVersionUID} for a serialisable class is missing.
331     */
332    SERIAL( "serial" ),
333
334    /**
335     *  The {@code SerialVersionUID} for a serialisable class is missing.
336     */
337    SIMPLIFY_STREAM_API_CALL_CHAIN( "SimplifyStreamApiCallChains" ),
338
339    /**
340     *  A static method is one class is exclusively called by one single other
341     *  class. Usually this means that this method should be moved into the
342     *  calling class.
343     */
344    STATIC_METHOD_ONLY_USED_IN_ONE_CLASS( "StaticMethodOnlyUsedInOneClass" ),
345
346    /**
347     *  The type of the argument to the {@code toArray()} method is unexpected.
348     */
349    SUSPICIOUS_TO_ARRAY_CALL( "SuspiciousToArrayCall" ),
350
351    /**
352     *  A thrown exception is caught within the same method.
353     */
354    THROW_CAUGHT_LOCALLY( "ThrowCaughtLocally" ),
355
356    /**
357     *  {@summary A more general type could be used} (e.g.
358     *  {@link CharSequence}
359     *  instead of
360     *  {@link String}
361     *  or
362     *  {@link java.util.Collection}
363     *  instead of
364     *  {@link java.util.List}).
365     */
366    TYPE_MAY_BE_WEAKENED( "TypeMayBeWeakened" ),
367
368    /**
369     *  The type for the argument looks not correct.
370     */
371    UNLIKELY_ARG_TYPE( "unlikely-arg-type" ),
372
373    /**
374     *  The method or statement is performing an unchecked conversion for a
375     *  generic data type.
376     */
377    UNCHECKED( "unchecked" ),
378
379    /**
380     *  An element is not used. This could be a parameter, a field, a private
381     *  method …
382     */
383    UNUSED( "unused" ),
384
385    /**
386     *  No caller of the method ever regards its return value.
387     */
388    UNUSED_RETURN_VALUE( "UnusedReturnValue" ),
389
390    /**
391     *  <p>{@summary A concrete class is used for an attribute, a return value
392     *  or an argument.} For a better decoupling, the use of interfaces only
393     *  is preferred.
394     */
395    USE_OF_CONCRETE_CLASS( "UseOfConcreteClass" );
396
397        /*------------*\
398    ====** Attributes **=======================================================
399        \*------------*/
400    /**
401     *  The text for the suppressable warning as added to the annotation.
402     */
403    private final String m_Text;
404
405        /*--------------*\
406    ====** Constructors **=====================================================
407        \*--------------*/
408    /**
409     *  Creates a new {@code SuppressableWarnings} instance.
410     *
411     *  @param  text    The text for the suppressable warning as added to the
412     *      annotation.
413     */
414    private SuppressableWarnings( final String text )
415    {
416        m_Text = text.intern();
417    }   //  SuppressableWarnings()
418
419        /*---------*\
420    ====** Methods **==========================================================
421        \*---------*/
422    /**
423     *  Creates an
424     *  {@link AnnotationSpec}
425     *  instance for the
426     *  {@link SuppressWarnings &#64;SuppressWarnings}
427     *  annotation.
428     *
429     *  @param  factory The factory for the JavaComposer artefacts.
430     *  @param  suppress    The tokens for the warnings to suppress.
431     *  @return The annotation specification.
432     */
433    @SuppressWarnings( "UseOfConcreteClass" )
434    public static final AnnotationSpec createSuppressWarningsAnnotation( final JavaComposer factory, final SuppressableWarnings... suppress )
435    {
436        final var argument = requireNonNullArgument( suppress, "suppress" ).length == 1
437            ? format( "\"%s\"", suppress [0] )
438            : stream( suppress )
439                .map( SuppressableWarnings::toString )
440                .sorted()
441                .collect( joining( "\", \"", "{\"", "\"}" ) );
442        final var retValue = requireNonNullArgument( factory, "factory" ).annotationBuilder( SuppressWarnings.class )
443            .forceInline( true )
444            .addMember( "value", argument )
445            .build();
446
447        //---* Done *----------------------------------------------------------
448        return retValue;
449    }   //  createSuppressWarningsAnnotation()
450
451    /**
452     *  Creates a comment (for IntelliJ IDEA) that suppresses the given
453     *  warnings.
454     *
455     *  @param  factory The factory for the JavaComposer artefacts.
456     *  @param  suppress    The tokens for the warnings to suppress.
457     *  @return The
458     *      {@link CodeBlock}
459     *      with the comment.
460     */
461    @SuppressWarnings( "UseOfConcreteClass" )
462    public static final CodeBlock createSuppressWarningsCommentForIDEA( final JavaComposer factory, final SuppressableWarnings... suppress )
463    {
464        requireNonNullArgument( factory, "factory" );
465
466        final var joiner = new StringJoiner( " ", "//noinspection ", EMPTY_STRING ).setEmptyValue( EMPTY_STRING );
467        for( final var warning : requireNonNullArgument( suppress, "suppress" ) )
468        {
469            joiner.add( warning.toString() );
470        }
471        final var retValue = joiner.length() > 0 ? factory.codeBlockOf( joiner.toString() ) : factory.emptyCodeBlock();
472
473        //---* Done *----------------------------------------------------------
474        return retValue;
475    }   //  createSuppressWarningsCommentForIDEA()
476
477    /**
478     *  {@inheritDoc}
479     */
480    @Override
481    public final String toString() { return m_Text; }
482}
483//  class SuppressableWarnings
484
485/*
486 *  End of File
487 */