View Javadoc

1   /***
2    * Copyright 2007 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * For more information visit
11   *         http://72miles.com and
12   *         http://architecturerules.googlecode.com/svn/docs/index.html
13   */
14  
15  package com.seventytwomiles.architecturerules.domain;
16  
17  
18  import com.seventytwomiles.architecturerules.exceptions.IllegalArchitectureRuleException;
19  import junit.framework.Assert;
20  import org.apache.commons.logging.Log;
21  import org.apache.commons.logging.LogFactory;
22  
23  import static java.lang.String.format;
24  import java.util.Collection;
25  import java.util.Collections;
26  import java.util.HashSet;
27  
28  
29  
30  /***
31   * <p>Represents a <code>Rule</code> that may not be violoated.</p>
32   *
33   * @author mikenereson
34   */
35  public class Rule {
36  
37  
38      protected static final Log log = LogFactory.getLog(Rule.class);
39  
40      /***
41       * <p>Unique id of this Rule as defined. Used to refer to this Rule in
42       * messages.</p>
43       *
44       * @parameter id String
45       */
46      private String id;
47  
48      /***
49       * <p>Collection of Strings. These Strings are package names. The names of
50       * the packages that will be check against the {@link #violations}. These
51       * packages may NOT depend upon the packages listed in violations.</p>
52       *
53       * @parameter violations Collection
54       */
55      private final Collection<JPackage> packages = new HashSet<JPackage>();
56  
57      /***
58       * <p>Comment about this rule that could be used in messages or just to make
59       * the configuration file more readable.</p>
60       *
61       * @parameter comment String
62       */
63      private String comment;
64  
65      /***
66       * <p>Collection of Strings. These Strings are package names. The names of
67       * the packages that the {@link #packages} may NOT depend upon</p>
68       *
69       * @parameter violations Collection
70       */
71      private final Collection<JPackage> violations = new HashSet<JPackage>();
72  
73  
74      /***
75       * <p>Constructs a new Rule.</p>
76       */
77      public Rule() {
78          // do nothing
79      }
80  
81  
82      /***
83       * <p>Instantiates a new Rule with the given <tt>id</tt>.</p>
84       *
85       * @param id sets the {@link #id}
86       */
87      public Rule(final String id) {
88  
89          this.id = id;
90      }
91  
92  
93      /***
94       * <p>Instantiates a new Rule with the given <tt>id</tt>.</p>
95       *
96       * <p>This constructor is to provide some sense of backwards compatibility
97       * with the releases in series 1 (1.0 and 1.1)</p>
98       *
99       * @param id sets the {@link #id}
100      * @param packageName a {@link @packages} to assert on.
101      */
102     public Rule(final String id, final String packageName) {
103 
104         setId(id);
105         addPackage(packageName);
106     }
107 
108 
109     /***
110      * <p>Setter for property {@link #id}.</p>
111      *
112      * @param id Value to set for property <tt>id</tt>.
113      * @return Rule this <code>Rule</code> to allow for method chaining.
114      */
115     public Rule setId(final String id) {
116 
117         Assert.assertNotNull("id can not be null", id);
118         Assert.assertFalse("id can not be empty", "".equals(id));
119 
120         this.id = id;
121 
122         return this;
123     }
124 
125 
126     /***
127      * <p>Adds a package to the Packages collection.</p>
128      *
129      * @param packageName String
130      * @return boolean <tt>true</tt> when the package is actually added to the
131      *         Collection. <tt>false</tt> would be returned if the package was
132      *         already in the Collection of packages.
133      */
134     public boolean addPackage(final String packageName) {
135 
136         Assert.assertNotNull(
137                 "null packageName can not be added",
138                 packageName);
139 
140         Assert.assertFalse(
141                 "empty packageName can not be added",
142                 packageName.equals(""));
143 
144         final JPackage jPackage = new JPackage(packageName);
145 
146         if (violations.contains(jPackage))
147             throw new IllegalArchitectureRuleException(
148                     "Could not add " + packageName + " package because " +
149                             "there is already a violation with the same " +
150                             "package name for rule " + id);
151 
152         return packages.add(jPackage);
153     }
154 
155 
156     /***
157      * <p>Getter for property {@link #comment}.</p>
158      *
159      * @return Value for property <tt>comment</tt>.
160      */
161     public String getComment() {
162         return this.comment;
163     }
164 
165 
166     /***
167      * <p>Getter for property {@link #id}.</p>
168      *
169      * @return Value for property <tt>id</tt>.
170      */
171     public String getId() {
172         return this.id;
173     }
174 
175 
176     /***
177      * <p>Getter for property {@link #packages}</p>
178      *
179      * @return Value for property <tt>packages</tt>.
180      */
181     public Collection<JPackage> getPackages() {
182         return this.packages;
183     }
184 
185 
186     /***
187      * @see Object#equals(Object)
188      */
189     @Override
190     public boolean equals(final Object object) {
191 
192         if (this == object)
193             return true;
194 
195         if (object == null)
196             return false;
197 
198         if (!(object instanceof Rule))
199             return false;
200 
201         final Rule that = (Rule) object;
202 
203         if (id != null ? !id.equals(that.getId()) : that.getId() != null)
204             return false;
205 
206         return true;
207     }
208 
209 
210     /***
211      * @see Object#hashCode()
212      */
213     @Override
214     public int hashCode() {
215         return (id != null ? id.hashCode() : 0);
216     }
217 
218 
219     /***
220      * <p>Add a new violation to this <code>Rule</code>.</p>
221      *
222      * @param violation String a package this this Rule's package may NOT depend
223      * upon
224      * @return Rule this <code>Rule</code> to allow for method chaining.
225      * @throws IllegalArchitectureRuleException a RuntimeException when the
226      * violation could not be added because the violation is one of the packages
227      * being checked.
228      */
229     public Rule addViolation(final JPackage violation) {
230 
231         Assert.assertNotNull("null violation can not be added", violation);
232         Assert.assertFalse("empty violation can not be added",
233                 violation.getPath().equals(""));
234 
235         if (packages.contains(violation))
236             throw new IllegalArchitectureRuleException(
237                     "Could not add architecture rule violation that creates rule " +
238                             "that says a package can not use itself. Remove " +
239                             "<violation>" + violation + "</violation> " +
240                             "from rule " + id);
241 
242         final boolean added = violations.add(violation);
243 
244         if (added) {
245 
246             final String debug = format("added violation %s to Rule %s",
247                     violation, id);
248 
249             log.debug(debug);
250 
251         } else {
252 
253             final String warn = format("failed to add violation %s to Rule %s",
254                     violation, id);
255 
256             log.warn(warn);
257         }
258 
259         return this;
260     }
261 
262 
263     /***
264      * <p>Add a new violation to this <code>Rule</code>.</p>
265      *
266      * @param violation String a package this this Rule's package may NOT depend
267      * upon
268      * @return Rule this <code>Rule</code> to allow for method chaining.
269      * @throws IllegalArchitectureRuleException a RuntimeException when the
270      * violation could not be added because the violation is one of the packages
271      * being checked.
272      */
273     public Rule addViolation(final String violation) {
274 
275         Assert.assertNotNull("null violation can not be added", violation);
276         Assert.assertFalse("empty violation can not be added",
277                 violation.equals(""));
278 
279         final JPackage violationPackage = new JPackage(violation);
280         return this.addViolation(violationPackage);
281     }
282 
283 
284     /***
285      * <p>Describes the properties of this rule in an xml-like format.</p>
286      *
287      * @return String of xml that describes this <code>Rule</code>.
288      */
289     public String describe() {
290 
291         return describe(false);
292     }
293 
294 
295     /***
296      * <p>Describes the properties of this rule in an xml-like format.</p>
297      *
298      * @param outputToConsole boolean <tt>true</tt> to output the description to
299      * the console
300      * @return String of xml that describes this <code>Rule</code>.
301      */
302     private String describe(final boolean outputToConsole) {
303 
304         final StringBuffer builder = new StringBuffer();
305 
306         builder.append("<rule>").append("\r\n");
307 
308         builder.append("\t")
309                 .append("<id>")
310                 .append(id)
311                 .append("</id>")
312                 .append("\r\n");
313 
314         builder.append("\t")
315                 .append("<packages>")
316                 .append(packages)
317                 .append("</packages>")
318                 .append("\r\n");
319 
320         builder.append("\t")
321                 .append("<comment>")
322                 .append(comment)
323                 .append("</comment>")
324                 .append("\r\n");
325 
326         builder.append("\t").append("<violations>").append("\r\n");
327 
328         for (final JPackage violation : violations) {
329             builder.append("\t\t").append("<violation>");
330             builder.append(violation.getPath());
331             builder.append("</violation>");
332             builder.append("\r\n");
333         }
334 
335         builder.append("\t").append("</violations>").append("\r\n");
336         builder.append("</rule>").append("\r\n");
337 
338         if (outputToConsole)
339             System.out.println(builder.toString());
340 
341         return builder.toString();
342     }
343 
344 
345     /***
346      * <p>Creates a String representation of this <code>Rule</code>. Useful for
347      * debugging and logging.</p>
348      *
349      * @return String describes this rule at its current state. Such as
350      *         <samp>['dao' for 'com.company.dao, com.company.dao.hibernate']</samp>
351      */
352     public String getDescriptionOfRule() {
353 
354         final String ruleDescription = "['{0}' for {1}] "
355                 .replaceAll("//{0}", getId())
356                 .replaceAll("//{1}", describePackages());
357 
358         return ruleDescription;
359     }
360 
361 
362     /***
363      * <p>Creates a String representation of this <tt>packages</tt>. Useful for
364      * debugging and logging.</p>
365      *
366      * @return String describes this Rule's packages. Such as
367      *         <samp>com.company.dao, com.company.dao.hibernate</samp>
368      */
369     public String describePackages() {
370 
371         final StringBuffer packagesDescription = new StringBuffer();
372 
373         final Object[] packagesArray = packages.toArray();
374         final int totalPackages = packagesArray.length;
375 
376         for (int i = 0; i < totalPackages; i++) {
377 
378             final JPackage jPackage = (JPackage) packagesArray[i];
379             final String packageName = jPackage.getPath();
380 
381             packagesDescription
382                     .append(packageName.trim())
383                     .append(" ");
384 
385             if (i + 1 < totalPackages)
386                 packagesDescription.append(",");
387         }
388 
389         final String description = packagesDescription.toString()
390                 .replaceAll("//[", "")
391                 .replaceAll("//]", "");
392 
393         return description;
394     }
395 
396 
397     /***
398      * <p>Get all of the <tt>violations</tt>.</p>
399      *
400      * <p>Note: this Collection is unmodifiable, use {@link #addViolation} and
401      * {@link #removeViolation}</p>
402      *
403      * @return Collection unmodifiable
404      * @throws UnsupportedOperationException when <code>getViolations.add(Object)</code>
405      * or <code>getViolations.remove(Object)</code> is called. Use {@link
406      * #addViolation} and {@link #removeViolation}.
407      */
408     public Collection<JPackage> getViolations() {
409 
410         return Collections.unmodifiableCollection(violations);
411     }
412 
413 
414     /***
415      * <p>Remove a package from this Rule.</p>
416      *
417      * @param packageName String a package this this Rule's package should not
418      * test on
419      * @return Rule this <code>Rule</code> to allow for method chaining.
420      */
421     public Rule removePackage(final String packageName) {
422 
423         Assert.assertNotNull(
424                 "null packageName can not be removed",
425                 packageName);
426 
427         Assert.assertFalse(
428                 "empty packageName can not be removed",
429                 "".equals(packageName));
430 
431         final JPackage jPackage = new JPackage(packageName);
432         final boolean removed = packages.remove(jPackage);
433 
434         if (removed) {
435 
436             final String debug = format(
437                     "removed package %s from Rule %s",
438                     packageName, this.id);
439 
440             log.debug(debug);
441 
442         } else {
443 
444             final String warn = format(
445                     "failed to remove package %s from Rule %s ",
446                     packageName, this.id);
447 
448             log.warn(warn);
449         }
450 
451         return this;
452     }
453 
454 
455     /***
456      * <p>Remove a violation from this Rule.</p>
457      *
458      * @param violation String a package this this Rule's package should not
459      * test on
460      * @return Rule this <code>Rule</code> to allow for method chaining.
461      */
462     public Rule removeViolation(final String violation) {
463 
464         Assert.assertFalse(
465                 "empty violation can not be removed",
466                 "".equals(violation));
467 
468         final JPackage jPackage = new JPackage(violation);
469         return removeViolation(jPackage);
470     }
471 
472 
473     public Rule removeViolation(final JPackage violation) {
474 
475         Assert.assertNotNull("null violation can not be removed", violation);
476 
477         final boolean removed = violations.remove(violation);
478 
479         if (removed) {
480 
481             final String debug = format(
482                     "removed violation %s from Rule %s",
483                     violation, this.id);
484 
485             log.debug(debug);
486 
487         } else {
488 
489             final String warn = format(
490                     "failed to remove violation %s from Rule %s ",
491                     violation, this.id);
492 
493             log.warn(warn);
494         }
495 
496         return this;
497     }
498 
499 
500     /***
501      * <p>Setter for property {@link #comment}.</p>
502      *
503      * @param comment Value to set for property <tt>comment</tt>.
504      * @return Rule this <code>Rule</code> to allow for method chaining.
505      */
506     public Rule setComment(final String comment) {
507 
508         Assert.assertNotNull("comment can not be null", comment);
509 
510         this.comment = comment;
511 
512         return this;
513     }
514 
515 
516     /***
517      * <p>Same as {@link #setId(String)}. The <code>DigesterConfiguraitonFactory</code>
518      * that builds the <code>Configuration<code> class can invoke void setter.
519      * When we added method chaining at version 2.1.0, we made <tt>setId</tt>
520      * return <code>Rule</code>, and the configuration factory broke. So this
521      * method was created to allow for the ConfigurationFactory to work, and for
522      * method chaining to be supported.</p>
523      *
524      * @param id Value to set for property <tt>id</tt>.
525      */
526     public void setIdString(final String id) {
527 
528         Assert.assertNotNull("id can not be null", id);
529         Assert.assertFalse("id can not be empty", "".equals(id));
530 
531         this.id = id;
532     }
533 }