1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.api;
21  
22  import static org.junit.Assert.assertArrayEquals;
23  import static org.junit.Assert.assertEquals;
24  import static org.junit.Assert.assertNull;
25  import static org.junit.Assert.assertTrue;
26  import static org.junit.Assert.fail;
27  
28  import java.lang.reflect.Field;
29  import java.lang.reflect.InvocationTargetException;
30  import java.net.URI;
31  import java.util.Arrays;
32  import java.util.regex.Pattern;
33  
34  import org.apache.commons.beanutils.ConversionException;
35  import org.apache.commons.beanutils.ConvertUtilsBean;
36  import org.apache.commons.beanutils.Converter;
37  import org.junit.Test;
38  import org.powermock.reflect.Whitebox;
39  
40  import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
41  import com.puppycrawl.tools.checkstyle.DefaultContext;
42  import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifier;
43  
44  public class AutomaticBeanTest {
45  
46      @Test
47      public void testConfigureNoSuchAttribute() {
48          final TestBean testBean = new TestBean();
49          final DefaultConfiguration conf = new DefaultConfiguration("testConf");
50          conf.addAttribute("NonExistent", "doesn't matter");
51          try {
52              testBean.configure(conf);
53              fail("Exception is expected");
54          }
55          catch (CheckstyleException ex) {
56              assertNull("Exceptions cause should be null", ex.getCause());
57              assertEquals("Invalid exception message",
58                      "Property 'NonExistent' does not exist, please check the documentation",
59                      ex.getMessage());
60          }
61      }
62  
63      @Test
64      public void testConfigureNoSuchAttribute2() {
65          final TestBean testBean = new TestBean();
66          final DefaultConfiguration conf = new DefaultConfiguration("testConf");
67          conf.addAttribute("privateField", "doesn't matter");
68          try {
69              testBean.configure(conf);
70              fail("Exception is expected");
71          }
72          catch (CheckstyleException ex) {
73              assertNull("Exceptions cause should be null", ex.getCause());
74              assertEquals("Invalid exception message",
75                      "Property 'privateField' does not exist, please check the documentation",
76                      ex.getMessage());
77          }
78      }
79  
80      @Test
81      public void testSetupChildFromBaseClass() throws CheckstyleException {
82          final TestBean testBean = new TestBean();
83          testBean.configure(new DefaultConfiguration("bean config"));
84          testBean.setupChild(null);
85          try {
86              testBean.setupChild(new DefaultConfiguration("dummy"));
87              fail("Exception expected");
88          }
89          catch (CheckstyleException ex) {
90              final String expectedMessage = "dummy is not allowed as a child in bean config. "
91                      + "Please review 'Parent Module' section for this Check"
92                      + " in web documentation if Check is standard.";
93              assertEquals("Invalid exception message", expectedMessage, ex.getMessage());
94          }
95      }
96  
97      @Test
98      public void testSetupInvalidChildFromBaseClass() throws Exception {
99          final TestBean testBean = new TestBean();
100         final DefaultConfiguration parentConf = new DefaultConfiguration("parentConf");
101         final DefaultConfiguration childConf = new DefaultConfiguration("childConf");
102         final Field field = AutomaticBean.class.getDeclaredField("configuration");
103         field.setAccessible(true);
104         field.set(testBean, parentConf);
105 
106         try {
107             testBean.setupChild(childConf);
108             fail("expecting checkstyle exception");
109         }
110         catch (CheckstyleException ex) {
111             assertEquals("expected exception", "childConf is not allowed as a "
112                             + "child in parentConf. Please review 'Parent Module' section "
113                             + "for this Check in web documentation if Check is standard.",
114                     ex.getMessage());
115         }
116     }
117 
118     @Test
119     public void testContextualizeInvocationTargetException() {
120         final TestBean testBean = new TestBean();
121         final DefaultContext context = new DefaultContext();
122         context.add("exceptionalMethod", 123.0f);
123         try {
124             testBean.contextualize(context);
125             fail("InvocationTargetException is expected");
126         }
127         catch (CheckstyleException ex) {
128             final String expected = "Cannot set property ";
129             assertTrue("Invalid exception cause, should be: InvocationTargetException",
130                     ex.getCause() instanceof InvocationTargetException);
131             assertTrue("Invalid exception message, should start with: " + expected,
132                     ex.getMessage().startsWith(expected));
133         }
134     }
135 
136     @Test
137     public void testContextualizeConversionException() {
138         final TestBean testBean = new TestBean();
139         final DefaultContext context = new DefaultContext();
140         context.add("val", "some string");
141         try {
142             testBean.contextualize(context);
143             fail("InvocationTargetException is expected");
144         }
145         catch (CheckstyleException ex) {
146             final String expected = "illegal value ";
147             assertTrue("Invalid exception cause, should be: ConversionException",
148                     ex.getCause() instanceof ConversionException);
149             assertTrue("Invalid exception message, should start with: " + expected,
150                     ex.getMessage().startsWith(expected));
151         }
152     }
153 
154     @Test
155     public void testTestBean() {
156         final TestBean testBean = new TestBean();
157         testBean.setVal(0);
158         testBean.setWrong("wrongVal");
159         testBean.assignPrivateFieldSecretly(null);
160         try {
161             testBean.setExceptionalMethod("someValue");
162             fail("exception expected");
163         }
164         catch (IllegalStateException ex) {
165             assertEquals("Invalid exception message",
166                     "null,wrongVal,0,someValue", ex.getMessage());
167         }
168     }
169 
170     @Test
171     public void testRegisterIntegralTypes() throws Exception {
172         final ConvertUtilsBeanStub convertUtilsBean = new ConvertUtilsBeanStub();
173         Whitebox.invokeMethod(AutomaticBean.class, "registerIntegralTypes", convertUtilsBean);
174         assertEquals("Number of converters registered differs from expected",
175                 81, convertUtilsBean.getRegisterCount());
176     }
177 
178     @Test
179     public void testBeanConverters() throws Exception {
180         final ConverterBean bean = new ConverterBean();
181 
182         // methods are not seen as used by reflection
183         bean.setStrings("BAD");
184         bean.setPattern(null);
185         bean.setSeverityLevel(null);
186         bean.setScope(null);
187         bean.setUri(null);
188         bean.setAccessModifiers(AccessModifier.PACKAGE);
189 
190         final DefaultConfiguration config = new DefaultConfiguration("bean");
191         config.addAttribute("strings", "a, b, c");
192         config.addAttribute("pattern", ".*");
193         config.addAttribute("severityLevel", "error");
194         config.addAttribute("scope", "public");
195         config.addAttribute("uri", "http://github.com");
196         config.addAttribute("accessModifiers", "public, private");
197         bean.configure(config);
198 
199         assertArrayEquals("invalid result", new String[] {"a", "b", "c"}, bean.strings);
200         assertEquals("invalid result", ".*", bean.pattern.pattern());
201         assertEquals("invalid result", SeverityLevel.ERROR, bean.severityLevel);
202         assertEquals("invalid result", Scope.PUBLIC, bean.scope);
203         assertEquals("invalid result", new URI("http://github.com"), bean.uri);
204         assertArrayEquals("invalid result",
205                 new AccessModifier[] {AccessModifier.PUBLIC, AccessModifier.PRIVATE},
206                 bean.accessModifiers);
207     }
208 
209     @Test
210     public void testBeanConvertersUri2() throws Exception {
211         final ConverterBean bean = new ConverterBean();
212         final DefaultConfiguration config = new DefaultConfiguration("bean");
213         config.addAttribute("uri", "");
214         bean.configure(config);
215 
216         assertNull("invalid result", bean.uri);
217     }
218 
219     @Test
220     public void testBeanConvertersUri3() {
221         final ConverterBean bean = new ConverterBean();
222         final DefaultConfiguration config = new DefaultConfiguration("bean");
223         config.addAttribute("uri", "BAD");
224 
225         try {
226             bean.configure(config);
227             fail("Exception is expected");
228         }
229         catch (CheckstyleException ex) {
230             assertEquals("Error message is not expected",
231                     "illegal value 'BAD' for property 'uri'", ex.getMessage());
232         }
233     }
234 
235     private static class ConvertUtilsBeanStub extends ConvertUtilsBean {
236 
237         private int registerCount;
238 
239         @Override
240         public void register(Converter converter, Class<?> clazz) {
241             super.register(converter, clazz);
242             if (converter != null) {
243                 registerCount++;
244             }
245         }
246 
247         public int getRegisterCount() {
248             return registerCount;
249         }
250 
251     }
252 
253     private static class TestBean extends AutomaticBean {
254 
255         private String privateField;
256 
257         private String wrong;
258 
259         private int val;
260 
261         public void setWrong(String wrong) {
262             this.wrong = wrong;
263         }
264 
265         public void setVal(int val) {
266             this.val = val;
267         }
268 
269         public void assignPrivateFieldSecretly(String input) {
270             privateField = input;
271         }
272 
273         public void setExceptionalMethod(String value) {
274             throw new IllegalStateException(privateField + "," + wrong + "," + val + "," + value);
275         }
276 
277         @Override
278         protected void finishLocalSetup() {
279             // No code by default
280         }
281 
282     }
283 
284     /**
285      * This class has to be public for reflection to access the methods.
286      */
287     public static class ConverterBean extends AutomaticBean {
288 
289         private String[] strings;
290         private Pattern pattern;
291         private SeverityLevel severityLevel;
292         private Scope scope;
293         private URI uri;
294         private AccessModifier[] accessModifiers;
295 
296         /**
297          * Setter for strings.
298          * @param strings strings.
299          */
300         public void setStrings(String... strings) {
301             this.strings = Arrays.copyOf(strings, strings.length);
302         }
303 
304         /**
305          * Setter for pattern.
306          * @param pattern pattern.
307          */
308         public void setPattern(Pattern pattern) {
309             this.pattern = pattern;
310         }
311 
312         /**
313          * Setter for severity level.
314          * @param severityLevel severity level.
315          */
316         public void setSeverityLevel(SeverityLevel severityLevel) {
317             this.severityLevel = severityLevel;
318         }
319 
320         /**
321          * Setter for scope.
322          * @param scope scope.
323          */
324         public void setScope(Scope scope) {
325             this.scope = scope;
326         }
327 
328         /**
329          * Setter for uri.
330          * @param uri uri.
331          */
332         public void setUri(URI uri) {
333             this.uri = uri;
334         }
335 
336         /**
337          * Setter for access modifiers.
338          * @param accessModifiers access modifiers.
339          */
340         public void setAccessModifiers(AccessModifier... accessModifiers) {
341             this.accessModifiers = Arrays.copyOf(accessModifiers, accessModifiers.length);
342         }
343 
344         @Override
345         protected void finishLocalSetup() {
346             // no code
347         }
348 
349     }
350 
351 }