LightJason - AgentSpeak(L++)
common/CCommon.java
Go to the documentation of this file.
1 /*
2  * @cond LICENSE
3  * ######################################################################################
4  * # LGPL License #
5  * # #
6  * # This file is part of the LightJason AgentSpeak(L++) #
7  * # Copyright (c) 2015-19, LightJason (info@lightjason.org) #
8  * # This program is free software: you can redistribute it and/or modify #
9  * # it under the terms of the GNU Lesser General Public License as #
10  * # published by the Free Software Foundation, either version 3 of the #
11  * # License, or (at your option) any later version. #
12  * # #
13  * # This program is distributed in the hope that it will be useful, #
14  * # but WITHOUT ANY WARRANTY; without even the implied warranty of #
15  * # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
16  * # GNU Lesser General Public License for more details. #
17  * # #
18  * # You should have received a copy of the GNU Lesser General Public License #
19  * # along with this program. If not, see http://www.gnu.org/licenses/ #
20  * ######################################################################################
21  * @endcond
22  */
23 
24 package org.lightjason.agentspeak.common;
25 
26 import com.google.common.reflect.ClassPath;
27 import org.apache.commons.lang3.tuple.ImmutablePair;
28 import org.apache.commons.lang3.tuple.Pair;
35 
36 import javax.annotation.Nonnull;
37 import javax.annotation.Nullable;
38 import java.io.File;
39 import java.io.IOException;
40 import java.io.InputStream;
41 import java.io.InputStreamReader;
42 import java.io.UncheckedIOException;
43 import java.lang.reflect.InvocationTargetException;
44 import java.lang.reflect.Method;
45 import java.lang.reflect.Modifier;
46 import java.net.MalformedURLException;
47 import java.net.URISyntaxException;
48 import java.net.URL;
49 import java.net.URLConnection;
50 import java.text.MessageFormat;
51 import java.util.Arrays;
52 import java.util.Locale;
53 import java.util.MissingResourceException;
54 import java.util.Objects;
55 import java.util.PropertyResourceBundle;
56 import java.util.ResourceBundle;
57 import java.util.function.Predicate;
58 import java.util.logging.Logger;
59 import java.util.stream.Stream;
60 
61 
65 public final class CCommon
66 {
70  public static final String PACKAGEROOT = "org.lightjason.agentspeak";
74  private static final Logger LOGGER = CCommon.logger( CCommon.class );
78  private static final ResourceBundle LANGUAGE = ResourceBundle.getBundle(
79  MessageFormat.format( "{0}.{1}", PACKAGEROOT, "language" ),
80  Locale.getDefault(),
81  new CUTF8Control()
82  );
86  private static final ResourceBundle PROPERTIES = ResourceBundle.getBundle(
87  MessageFormat.format( "{0}.{1}", PACKAGEROOT, "configuration" ),
88  Locale.getDefault(),
89  new CUTF8Control()
90  );
91 
92 
96  private CCommon()
97  {}
98 
105  @Nonnull
106  public static Logger logger( final Class<?> p_class )
107  {
108  return Logger.getLogger( p_class.getName() );
109  }
110 
116  @Nonnull
117  public static String[] languages()
118  {
119  return Arrays.stream( PROPERTIES.getString( "translation" ).split( "," ) ).map( i -> i.trim().toLowerCase() ).toArray( String[]::new );
120  }
121 
127  @Nonnull
128  public static ResourceBundle languagebundle()
129  {
130  return LANGUAGE;
131  }
132 
138  @Nonnull
139  public static ResourceBundle configuration()
140  {
141  return PROPERTIES;
142  }
143 
144  // --- access to action instantiation ----------------------------------------------------------------------------------------------------------------------
145 
152  @Nonnull
153  public static Stream<IAction> actionsFromPackage( @Nullable final String... p_package )
154  {
155  return ( ( Objects.isNull( p_package ) ) || ( p_package.length == 0 )
156  ? Stream.of( MessageFormat.format( "{0}.{1}", PACKAGEROOT, "action.builtin" ) )
157  : Arrays.stream( p_package ) )
158  .flatMap( j ->
159  {
160  try
161  {
162  return ClassPath.from( Thread.currentThread().getContextClassLoader() )
163  .getTopLevelClassesRecursive( j )
164  .parallelStream()
165  .map( ClassPath.ClassInfo::load )
166  .filter( i -> !Modifier.isAbstract( i.getModifiers() ) )
167  .filter( i -> !Modifier.isInterface( i.getModifiers() ) )
168  .filter( i -> Modifier.isPublic( i.getModifiers() ) )
169  .filter( IAction.class::isAssignableFrom )
170  .map( i ->
171  {
172  try
173  {
174  return (IAction) i.getConstructor().newInstance();
175  }
176  catch ( final NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException l_exception )
177  {
178  LOGGER.warning( CCommon.languagestring( CCommon.class, "actioninstantiate", i, l_exception ) );
179  return null;
180  }
181  } )
182 
183  // action can be instantiate
184  .filter( Objects::nonNull )
185 
186  // check usable action name
187  .filter( CCommon::actionusable );
188  }
189  catch ( final IOException l_exception )
190  {
191  throw new UncheckedIOException( l_exception );
192  }
193  } );
194  }
195 
196 
204  @Nonnull
205  @SuppressWarnings( "unchecked" )
206  public static Stream<IAction> actionsFromAgentClass( @Nonnull final Class<?>... p_class )
207  {
208  return p_class.length == 0
209  ? Stream.empty()
210  : Arrays.stream( p_class )
211  .parallel()
212  .filter( IAgent.class::isAssignableFrom )
213  .flatMap( CCommon::methods )
214  .map( i ->
215  {
216  try
217  {
218  return (IAction) new CMethodAction( i );
219  }
220  catch ( final IllegalAccessException l_exception )
221  {
222  LOGGER.warning( CCommon.languagestring( CCommon.class, "actioninstantiate", i, l_exception ) );
223  return null;
224  }
225  } )
226 
227  // action can be instantiate
228  .filter( Objects::nonNull )
229 
230  // check usable action name
231  .filter( CCommon::actionusable );
232  }
233 
240  private static boolean actionusable( final IAction p_action )
241  {
242  if ( ( p_action.name().empty() ) || ( p_action.name().get( 0 ).trim().isEmpty() ) )
243  {
244  LOGGER.warning( CCommon.languagestring( CCommon.class, "actionnameempty" ) );
245  return false;
246  }
247 
248  if ( !Character.isLetter( p_action.name().get( 0 ).charAt( 0 ) ) )
249  {
250  LOGGER.warning( CCommon.languagestring( CCommon.class, "actionletter", p_action ) );
251  return false;
252  }
253 
254  if ( !Character.isLowerCase( p_action.name().get( 0 ).charAt( 0 ) ) )
255  {
256  LOGGER.warning( CCommon.languagestring( CCommon.class, "actionlowercase", p_action ) );
257  return false;
258  }
259 
260  return true;
261  }
262 
263 
271  @Nonnull
272  private static Stream<Method> methods( final Class<?> p_class )
273  {
274  final Pair<Boolean, IAgentAction.EAccess> l_classannotation = CCommon.isActionClass( p_class );
275  if ( !l_classannotation.getLeft() )
276  return Objects.isNull( p_class.getSuperclass() )
277  ? Stream.empty()
278  : methods( p_class.getSuperclass() );
279 
280  final Predicate<Method> l_filter = IAgentAction.EAccess.WHITELIST.equals( l_classannotation.getRight() )
281  ? i -> !CCommon.isActionFiltered( i, p_class )
282  : i -> CCommon.isActionFiltered( i, p_class );
283 
284  return Stream.concat(
285  Arrays.stream( p_class.getDeclaredMethods() )
286  .parallel()
287  .peek( i -> i.setAccessible( true ) )
288  .filter( i -> !Modifier.isAbstract( i.getModifiers() ) )
289  .filter( i -> !Modifier.isInterface( i.getModifiers() ) )
290  .filter( i -> !Modifier.isNative( i.getModifiers() ) )
291  .filter( i -> !Modifier.isStatic( i.getModifiers() ) )
292  .filter( l_filter ),
293  methods( p_class.getSuperclass() )
294  );
295  }
296 
303  @Nonnull
304  private static Pair<Boolean, IAgentAction.EAccess> isActionClass( final Class<?> p_class )
305  {
306  if ( !p_class.isAnnotationPresent( IAgentAction.class ) )
307  return new ImmutablePair<>( false, IAgentAction.EAccess.BLACKLIST );
308 
309  final IAgentAction l_annotation = p_class.getAnnotation( IAgentAction.class );
310  return new ImmutablePair<>(
311  ( l_annotation.classes().length == 0 )
312  || ( Arrays.stream( p_class.getAnnotation( IAgentAction.class ).classes() )
313  .parallel()
314  .anyMatch( p_class::equals )
315  ),
316  l_annotation.access()
317  );
318  }
319 
327  private static boolean isActionFiltered( final Method p_method, final Class<?> p_class )
328  {
329  return p_method.isAnnotationPresent( IAgentActionFilter.class )
330  && (
331  ( p_method.getAnnotation( IAgentActionFilter.class ).classes().length == 0 )
332  || ( Arrays.stream( p_method.getAnnotation( IAgentActionFilter.class ).classes() )
333  .parallel()
334  .anyMatch( p_class::equals )
335  )
336  );
337  }
338 
339  // --- resource access -------------------------------------------------------------------------------------------------------------------------------------
340 
351  @Nonnull
352  public static URL concaturl( final URL p_base, final String p_string ) throws MalformedURLException, URISyntaxException
353  {
354  return new URL( p_base.toString() + p_string ).toURI().normalize().toURL();
355  }
356 
362  @Nullable
363  public static URL resourceurl()
364  {
365  return CCommon.class.getClassLoader().getResource( "" );
366  }
367 
377  @Nonnull
378  public static URL resourceurl( final String p_file ) throws URISyntaxException, MalformedURLException
379  {
380  return resourceurl( new File( p_file ) );
381  }
382 
392  @Nonnull
393  private static URL resourceurl( final File p_file ) throws URISyntaxException, MalformedURLException
394  {
395  if ( p_file.exists() )
396  return p_file.toURI().normalize().toURL();
397 
398  final URL l_url = CCommon.class.getClassLoader().getResource( p_file.toString().replace( File.separator, "/" ) );
399  if ( Objects.isNull( l_url ) )
400  throw new CIllegalArgumentException( CCommon.languagestring( CCommon.class, "fileurlnull", p_file ) );
401  return l_url.toURI().normalize().toURL();
402  }
403 
404  // --- language operations ---------------------------------------------------------------------------------------------------------------------------------
405 
416  @Nonnull
417  public static <T> String languagestring( final T p_source, final String p_label, final Object... p_parameter )
418  {
419  return languagestring( p_source.getClass(), p_label, p_parameter );
420  }
421 
430  @Nonnull
431  public static String languagestring( final Class<?> p_class, final String p_label, final Object... p_parameter )
432  {
433  try
434  {
435  return MessageFormat.format( LANGUAGE.getString( languagelabel( p_class, p_label ) ), p_parameter );
436  }
437  catch ( final MissingResourceException l_exception )
438  {
439  return "";
440  }
441  }
442 
450  @Nonnull
451  private static String languagelabel( final Class<?> p_class, final String p_label )
452  {
453  return ( p_class.getCanonicalName().toLowerCase( Locale.ROOT ) + "." + p_label.toLowerCase( Locale.ROOT ) ).replaceAll( "[^a-zA-Z0-9_.]+", "" ).replace(
454  PACKAGEROOT + ".", "" );
455  }
456 
457  // --- resource utf-8 encoding -----------------------------------------------------------------------------------------------------------------------------
458 
464  private static final class CUTF8Control extends ResourceBundle.Control
465  {
466 
467  public final ResourceBundle newBundle( final String p_basename, final Locale p_locale, final String p_format, final ClassLoader p_loader,
468  final boolean p_reload
469  ) throws IllegalAccessException, InstantiationException, IOException
470  {
471  final InputStream l_stream;
472  final String l_resource = this.toResourceName( this.toBundleName( p_basename, p_locale ), "properties" );
473 
474  if ( !p_reload )
475  l_stream = p_loader.getResourceAsStream( l_resource );
476  else
477  {
478 
479  final URL l_url = p_loader.getResource( l_resource );
480  if ( Objects.isNull( l_url ) )
481  return null;
482 
483  final URLConnection l_connection = l_url.openConnection();
484  if ( Objects.isNull( l_connection ) )
485  return null;
486 
487  l_connection.setUseCaches( false );
488  l_stream = l_connection.getInputStream();
489  }
490 
491  final ResourceBundle l_bundle = new PropertyResourceBundle( new InputStreamReader( l_stream, "UTF-8" ) );
492  l_stream.close();
493  return l_bundle;
494  }
495  }
496 
497 }
Class<?> [] classes() default
list of classes for which the annotation is defined
static ResourceBundle configuration()
returns the property data of the package
CCommon()
private ctor - avoid instantiation
class annotation to set default behaviour of method-action-binding to a blacklist ...
static String [] languages()
list of usable languages
static URL resourceurl(final String p_file)
returns a file from a resource e.g.
static ResourceBundle languagebundle()
returns the language bundle
static< T > String languagestring(final T p_source, final String p_label, final Object... p_parameter)
returns the language depend string on any object
static URL resourceurl(final File p_file)
returns a file from a resource e.g.
String get(final int p_index)
returns an part of the path
static Stream< IAction > actionsFromAgentClass( @Nonnull final Class<?>... p_class)
returns actions by a class
external action interface
Definition: IAction.java:38
final ResourceBundle newBundle(final String p_basename, final Locale p_locale, final String p_format, final ClassLoader p_loader, final boolean p_reload)
static Logger logger(final Class<?> p_class)
returns a logger instance
static boolean actionusable(final IAction p_action)
checks if an action is usable
EAccess access() default EAccess.BLACKLIST
access of the action filter
static Stream< IAction > actionsFromPackage( @Nullable final String... p_package)
get all classes within an Java package as action
static URL resourceurl()
returns root path of the resource
IPath name()
returns the name with path of the action
static String languagestring(final Class<?> p_class, final String p_label, final Object... p_parameter)
returns a string of the resource file
static final ResourceBundle PROPERTIES
properties of the package
boolean empty()
check if the path is empty
static final ResourceBundle LANGUAGE
language resource bundle
static final String PACKAGEROOT
package name
static boolean isActionFiltered(final Method p_method, final Class<?> p_class)
class filter of an action to use it
static String languagelabel(final Class<?> p_class, final String p_label)
returns the label of a class and string to get access to the resource
static Pair< Boolean, IAgentAction.EAccess > isActionClass(final Class<?> p_class)
filter of a class to use it as action
static URL concaturl(final URL p_base, final String p_string)
concats an URL with a path
class to read UTF-8 encoded property file
method annotation to allow the binding of a method for an action
static Stream< Method > methods(final Class<?> p_class)
reads all methods by the action-annotations for building agent-actions