LightJason - AgentSpeak(L++)
TestCLanguageLabels.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;
25 
26 import com.github.javaparser.JavaParser;
27 import com.github.javaparser.ParseProblemException;
28 import com.github.javaparser.ast.PackageDeclaration;
29 import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
30 import com.github.javaparser.ast.body.EnumDeclaration;
31 import com.github.javaparser.ast.expr.MethodCallExpr;
32 import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
33 import org.apache.commons.lang3.ClassUtils;
34 import org.apache.commons.lang3.StringUtils;
35 import org.junit.Test;
37 
38 import java.io.File;
39 import java.io.FileInputStream;
40 import java.io.IOException;
41 import java.net.URI;
42 import java.nio.file.FileSystems;
43 import java.nio.file.Files;
44 import java.nio.file.Paths;
45 import java.text.MessageFormat;
46 import java.util.Arrays;
47 import java.util.Collections;
48 import java.util.HashMap;
49 import java.util.HashSet;
50 import java.util.Locale;
51 import java.util.Map;
52 import java.util.Properties;
53 import java.util.Set;
54 import java.util.regex.Matcher;
55 import java.util.regex.Pattern;
56 import java.util.stream.Collectors;
57 import java.util.stream.IntStream;
58 import java.util.stream.Stream;
59 
60 import static org.junit.Assert.assertFalse;
61 import static org.junit.Assert.assertTrue;
62 import static org.junit.Assume.assumeTrue;
63 
64 
68 public final class TestCLanguageLabels extends IBaseTest
69 {
70  private static final String CLASSSEPARATOR = ".";
74  private static final URI SEARCHPATH;
78  private static final Map<String, URI> LANGUAGEPROPERY = new HashMap<>();
79 
80  static
81  {
82  final String l_resource = "../../src/main/resources/";
83  URI l_uri = null;
84  try
85  {
86  l_uri = CCommon.concaturl( CCommon.resourceurl(), "../../src/main/java/" ).toURI();
87  }
88  catch ( final Exception l_exception )
89  {
90  assumeTrue( MessageFormat.format( "source directory cannot be read: {0}", l_exception.getMessage() ), false );
91  }
92 
93  // read all possible languages and define the first as default
94  final String[] l_languages = CCommon.languages();
95  IntStream.range( 0, l_languages.length )
96  .boxed()
97  .forEach( i ->
98  {
99  try
100  {
101  LANGUAGEPROPERY.put(
102  l_languages[i],
105  MessageFormat.format(
106  "{0}/{1}/{2}",
107  l_resource,
108  CCommon.PACKAGEROOT.replace( CLASSSEPARATOR, "/" ),
109  MessageFormat.format(
110  "language{0}.properties",
111  i == 0
112  ? ""
113  : "_" + l_languages[i]
114  )
115  )
116  ).toURI()
117  );
118  }
119  catch ( final Exception l_exception )
120  {
121  assumeTrue( MessageFormat.format( "source directory cannot be read: {0}", l_exception.getMessage() ), false );
122  }
123  } );
124 
125  SEARCHPATH = l_uri;
126  }
127 
131  @Test
132  public void testTranslation()
133  {
134  assumeTrue( "no languages are defined for checking", !LANGUAGEPROPERY.isEmpty() );
135 
136  // --- read language definitions from the configuration
137  final Set<String> l_translation = Collections.unmodifiableSet(
138  Arrays.stream( CCommon.configuration().getObject( "translation" ).toString().split( "," ) )
139  .map( i -> i.trim().toLowerCase() )
140  .collect( Collectors.toSet() )
141  );
142 
143 
144  // --- check if a test (language resource) exists for each definied language
145  final Set<String> l_translationtesting = new HashSet<>( l_translation );
146  l_translationtesting.removeAll( LANGUAGEPROPERY.keySet() );
147  assertFalse(
148  MessageFormat.format(
149  "configuration defines {1,choice,1#translation|1<translations} {0} that {1,choice,1#is|1<are} not tested",
150  l_translationtesting,
151  l_translationtesting.size()
152  ),
153  !l_translationtesting.isEmpty()
154  );
155 
156 
157  // --- check unused language resource files
158  final Set<String> l_translationusing = new HashSet<>( LANGUAGEPROPERY.keySet() );
159  l_translationusing.removeAll( l_translation );
160  assertFalse(
161  MessageFormat.format(
162  "{1,choice,1#translation|1<translations} {0} {1,choice,1#is|1<are} checked, which will not be used within the package configuration",
163  l_translationusing,
164  l_translationusing.size()
165  ),
166  !l_translationusing.isEmpty()
167  );
168  }
169 
175  @Test
176  public void testResourceString() throws IOException
177  {
178  assumeTrue( "no languages are defined for checking", !LANGUAGEPROPERY.isEmpty() );
179 
180  final Set<String> l_ignoredlabel = new HashSet<>();
181 
182  // --- parse source and get label definition
183  final Set<String> l_label = Collections.unmodifiableSet(
184  Files.walk( Paths.get( SEARCHPATH ) )
185  .filter( Files::isRegularFile )
186  .filter( i -> i.toString().endsWith( ".java" ) )
187  .flatMap( i ->
188  {
189  try
190  {
191  final CJavaVistor l_parser = new CJavaVistor();
192  l_parser.visit( JavaParser.parse( new FileInputStream( i.toFile() ) ), null );
193  return l_parser.labels().stream();
194  }
195  catch ( final IOException l_excpetion )
196  {
197  assertTrue( MessageFormat.format( "io error on file [{0}]: {1}", i, l_excpetion.getMessage() ), false );
198  return Stream.empty();
199  }
200  catch ( final ParseProblemException l_exception )
201  {
202  // add label build by class path to the ignore list
203  l_ignoredlabel.add(
204  i.toAbsolutePath().toString()
205  // remove path to class directory
206  .replace(
207  FileSystems.getDefault()
208  .provider()
209  .getPath( SEARCHPATH )
210  .toAbsolutePath()
211  .toString(),
212  ""
213  )
214  // string starts with path separator
215  .substring( 1 )
216  // remove file extension
217  .replace( ".java", "" )
218  // replace separators with dots
219  .replace( "/", CLASSSEPARATOR )
220  // convert to lower-case
221  .toLowerCase()
222  // remove package-root name
223  .replace( CCommon.PACKAGEROOT + CLASSSEPARATOR, "" )
224  );
225 
226  System.err.println( MessageFormat.format( "parsing error on file [{0}]:\n{1}", i, l_exception.getMessage() ) );
227  return Stream.empty();
228  }
229  } )
230  .collect( Collectors.toSet() )
231  );
232 
233  // --- check of any label is found
234  assertFalse( "translation labels are empty, check naming of translation method", l_label.isEmpty() );
235 
236  // --- check label towards the property definition
237  if ( l_ignoredlabel.size() > 0 )
238  System.err.println( MessageFormat.format( "labels that starts with {0} are ignored, because parsing errors are occurred", l_ignoredlabel ) );
239 
240  LANGUAGEPROPERY.forEach( ( k, v ) ->
241  {
242  try
243  (
244  final FileInputStream l_stream = new FileInputStream( new File( v ) )
245  )
246  {
247  final Properties l_property = new Properties();
248  l_property.load( l_stream );
249 
250  final Set<String> l_parseditems = new HashSet<>( l_label );
251  final Set<String> l_propertyitems = l_property.keySet().parallelStream().map( Object::toString ).collect(
252  Collectors.toSet() );
253 
254  // --- check if all property items are within the parsed labels
255  l_parseditems.removeAll( l_propertyitems );
256  assertTrue(
257  MessageFormat.format(
258  "the following {1,choice,1#key|1<keys} in language [{0}] {1,choice,1#is|1<are} not existing within the language file:\n{2}",
259  k,
260  l_parseditems.size(),
261  StringUtils.join( l_parseditems, ", " )
262  ),
263  l_parseditems.isEmpty()
264  );
265 
266 
267  // --- check if all parsed labels within the property item and remove ignored labels
268  l_propertyitems.removeAll( l_label );
269  final Set<String> l_ignoredpropertyitems = l_propertyitems.parallelStream()
270  .filter( j -> l_ignoredlabel.parallelStream()
271  .map( j::startsWith )
272  .allMatch( l -> false )
273  )
274  .collect( Collectors.toSet() );
275  assertTrue(
276  MessageFormat.format(
277  "the following {1,choice,1#key|1<keys} in language [{0}] {1,choice,1#is|1<are} not existing within the source code:\n{2}",
278  k,
279  l_ignoredpropertyitems.size(),
280  StringUtils.join( l_ignoredpropertyitems, ", " )
281  ),
282  l_ignoredpropertyitems.isEmpty()
283  );
284  }
285  catch ( final IOException l_exception )
286  {
287  assertTrue( MessageFormat.format( "io exception: {0}", l_exception.getMessage() ), false );
288  }
289  } );
290  }
291 
292 
293  // ---------------------------------------------------------------------------------------------------------------------------------------------------------
294 
298  private static class CJavaVistor extends VoidVisitorAdapter<Object>
299  {
303  private static final String TRANSLATEMETHODNAME = "CCommon.languagestring";
307  private static final Pattern LANGUAGEMETHODPATTERN = Pattern.compile( TRANSLATEMETHODNAME + ".+?\\)" );
311  private String m_innerclass = "";
315  private String m_outerclass = "";
319  private String m_package = "";
323  private final Set<String> m_label = new HashSet<>();
324 
330  final Set<String> labels()
331  {
332  return m_label;
333  }
334 
335  @Override
336  public void visit( final PackageDeclaration p_package, final Object p_arg )
337  {
338  m_package = p_package.getName().toString();
339  super.visit( p_package, p_arg );
340  }
341 
342  @Override
343  public void visit( final ClassOrInterfaceDeclaration p_class, final Object p_arg )
344  {
345  if ( m_outerclass.isEmpty() )
346  m_outerclass = p_class.getName().toString();
347  else
348  m_innerclass = p_class.getName().toString();
349 
350  super.visit( p_class, p_arg );
351 
352  m_innerclass = "";
353  }
354 
355  @Override
356  public void visit( final EnumDeclaration p_enum, final Object p_arg )
357  {
358  if ( m_outerclass.isEmpty() )
359  m_outerclass = p_enum.getName().toString();
360  else
361  m_innerclass = p_enum.getName().toString();
362 
363  super.visit( p_enum, p_arg );
364 
365  m_innerclass = "";
366  }
367 
368  @Override
369  public void visit( final MethodCallExpr p_methodcall, final Object p_arg )
370  {
371  final String l_label = this.label( p_methodcall.toString() );
372  if ( !l_label.isEmpty() )
373  m_label.add( l_label );
374 
375  super.visit( p_methodcall, p_arg );
376  }
377 
384  private String label( final String p_line )
385  {
386  final Matcher l_matcher = LANGUAGEMETHODPATTERN.matcher( p_line );
387  if ( !l_matcher.find() )
388  return "";
389 
390  final String[] l_split = l_matcher.group( 0 ).split( "," );
391  final String[] l_return = new String[2];
392 
393  // class name
394  l_return[0] = l_split[0].replace( TRANSLATEMETHODNAME, "" ).replace( "(", "" ).trim();
395  // label name
396  l_return[1] = l_split[1].replace( ")", "" ).replace( "\"", "" ).split( ";" )[0].trim().toLowerCase( Locale.ROOT );
397 
398  return (
399  "this".equals( l_return[0] )
400  ? buildlabel( m_package, m_outerclass, m_outerclass, m_innerclass, l_return[1] )
401  : buildlabel( m_package, m_outerclass, l_return[0].replace( ".class", "" ).replace( m_package + CLASSSEPARATOR, "" ), "", l_return[1] )
402  ).trim().toLowerCase( Locale.ROOT ).replace( CCommon.PACKAGEROOT + CLASSSEPARATOR, "" );
403 
404  }
405 
417  private static String buildlabel( final String p_package, final String p_outerclass1, final String p_outerclass2, final String p_innerclass, final String p_label )
418  {
419  final String l_outerclass = p_outerclass1.equals( p_outerclass2 )
420  ? p_outerclass1
421  : MessageFormat.format( "{0}{1}{2}", p_outerclass1, ClassUtils.PACKAGE_SEPARATOR, p_outerclass2 );
422 
423  return MessageFormat.format(
424  "{0}{1}{2}{3}{4}{5}",
425  p_package, ClassUtils.PACKAGE_SEPARATOR,
426  l_outerclass, p_innerclass.isEmpty() ? "" : ClassUtils.PACKAGE_SEPARATOR,
427  p_innerclass,
428 
429  p_label.isEmpty()
430  ? ""
431  : CLASSSEPARATOR + p_label
432  );
433  }
434 
435  }
436 
437 }
static ResourceBundle configuration()
returns the property data of the package
base test class with helpers
Definition: IBaseTest.java:33
static String [] languages()
list of usable languages
static String buildlabel(final String p_package, final String p_outerclass1, final String p_outerclass2, final String p_innerclass, final String p_label)
returns full qualified class name (inner & outer class)
void visit(final PackageDeclaration p_package, final Object p_arg)
void visit(final ClassOrInterfaceDeclaration p_class, final Object p_arg)
void visit(final MethodCallExpr p_methodcall, final Object p_arg)
static final String TRANSLATEMETHODNAME
method to translate strings
void testResourceString()
test-case all resource strings
String label(final String p_line)
gets the class name and label name
static URL resourceurl()
returns root path of the resource
void testTranslation()
check package translation configuration versus property items
final Set< String > labels()
returns the translated labels
static final String PACKAGEROOT
package name
static final Pattern LANGUAGEMETHODPATTERN
reg expression to extract label data
static URL concaturl(final URL p_base, final String p_string)
concats an URL with a path
void visit(final EnumDeclaration p_enum, final Object p_arg)
static final Map< String, URI > LANGUAGEPROPERY
property filenames with language data