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.services;
16  
17  /*
18   * Copyright 2007 the original author or authors.
19   *
20   * Licensed under the Apache License, Version 2.0 (the "License");
21   * you may not use this file except in compliance with the License.
22   * You may obtain a copy of the License at
23   *
24   * http://www.apache.org/licenses/LICENSE-2.0
25   *
26   * Unless required by applicable law or agreed to in writing, software
27   * distributed under the License is distributed on an "AS IS" BASIS,
28   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29   * See the License for the specific language governing permissions and
30   * limitations under the License.
31   *
32   * For more information visit
33   * http://architecturerules.googlecode.com/svn/docs/index.html
34   */
35  
36  
37  import com.seventytwomiles.architecturerules.configuration.Configuration;
38  import com.seventytwomiles.architecturerules.domain.JPackage;
39  import com.seventytwomiles.architecturerules.domain.SourceDirectory;
40  import com.seventytwomiles.architecturerules.exceptions.CyclicRedundancyException;
41  import com.seventytwomiles.architecturerules.exceptions.DependencyConstraintException;
42  import com.seventytwomiles.architecturerules.exceptions.NoPackagesFoundException;
43  import com.seventytwomiles.architecturerules.exceptions.SourceNotFoundException;
44  import jdepend.framework.JDepend;
45  import jdepend.framework.JavaPackage;
46  import org.apache.commons.logging.Log;
47  import org.apache.commons.logging.LogFactory;
48  
49  import java.io.File;
50  import java.io.IOException;
51  import java.util.Collection;
52  
53  
54  
55  /***
56   * <p>Provides support for any type of rule that can be asserted using the
57   * jdepend library</p>
58   *
59   * @author mikenereson
60   * @see RulesServiceImpl
61   * @see CyclicRedundancyServiceImpl
62   */
63  abstract class AbstractArchitecturalRules {
64  
65  
66      /***
67       * <p>log to debug with.</p>
68       *
69       * @parameter log Log
70       */
71      private static final Log log
72              = LogFactory.getLog(AbstractArchitecturalRules.class);
73      /***
74       * <p>The <code>Configuration</code> to test against.</p>
75       *
76       * @parameter configuration Configuration
77       */
78      final protected Configuration configuration;
79      /***
80       * <p>Instance of jdepend to assert architecture with</p>
81       *
82       * @parameter jdepend JDepend
83       */
84      private final JDepend jdepend;
85      /***
86       * <p>All packages that will be analyze</p>
87       *
88       * @parameter packages Collection
89       */
90      private Collection<JavaPackage> packages;
91  
92  
93      /***
94       * <p>Constructor that loads up the configuration and loads all the packages
95       * in the source paths</p>
96       *
97       * @param configuration Configuration
98       * @throws SourceNotFoundException when an required source directory does
99       * not exist and when <tt>exception</tt>=<tt>"true"</tt> in the source
100      * configuration
101      * @throws NoPackagesFoundException when none of the source directories
102      * exist and <tt>no-packages</tt>="<tt>ignore</tt>" in the sources
103      * configuration
104      */
105     AbstractArchitecturalRules(final Configuration configuration)
106             throws SourceNotFoundException, NoPackagesFoundException {
107         log.info("instantiating new AbstractArchitecturalRules");
108 
109         this.configuration = configuration;
110 
111         /* instantiate JDepend */
112         jdepend = new JDepend();
113 
114         /* read source paths from configuration file */
115         final Collection<SourceDirectory> sources
116                 = this.configuration.getSources();
117 
118         /* add each source to jdepend */
119         for (final SourceDirectory source : sources)
120             addSourceToJdepend(source);
121 
122         analyze();
123     }
124 
125 
126     /***
127      * <p>Add a sourceDirectory path to JDepend instance. Throws Exception when
128      * sourceDirectory path can not be found, if a given
129      * <code>SourceDirectory</code> is configured to do so.</p>
130      *
131      * @param sourceDirectory a <code>SourceDirectory</code> to read path from
132      */
133     private void addSourceToJdepend(final SourceDirectory sourceDirectory) {
134         final String sourcePath = sourceDirectory.getPath();
135         final StringBuffer message = new StringBuffer();
136 
137         final boolean throwExceptionWhenNotFound
138                 = sourceDirectory.shouldThrowExceptionWhenNotFound();
139 
140         try {
141             jdepend.addDirectory(sourcePath);
142 
143             if (log.isDebugEnabled()) {
144                 message.append("loaded ");
145                 message.append(throwExceptionWhenNotFound ? "required " : "");
146                 message.append("sourceDirectory ");
147                 message.append(new File("").getAbsolutePath());
148                 message.append("//");
149                 message.append(sourcePath);
150 
151                 log.debug(message.toString());
152             }
153         } catch (final IOException e) {
154             /* sourceDirectory not found */
155             if (log.isWarnEnabled()) {
156                 message.append(throwExceptionWhenNotFound ? "required " : "");
157                 message.append("sourceDirectory ");
158                 message.append(new File("").getAbsolutePath());
159                 message.append("//");
160                 message.append(sourcePath);
161                 message.append(" does not exist");
162 
163                 log.warn(message.toString());
164             }
165 
166             if (sourceDirectory.shouldThrowExceptionWhenNotFound()) {
167                 log.error(sourcePath + " was not found", e);
168 
169                 throw new SourceNotFoundException(
170                         sourcePath + " was not found", e);
171             }
172         }
173     }
174 
175 
176     /***
177      * <p>Analyze with JDepend. Call after JDepend knows about all of the source
178      * paths.</p>
179      */
180     private void analyze() {
181         /***
182          * Ask jdepend to analyze each package in each of the source
183          * directories that were added above.
184          */
185         log.debug("fetching packages");
186         packages = jdepend.analyze();
187 
188         log.debug("checking how many packages were found by JDepend");
189 
190         if (packages.isEmpty()) {
191             log.warn("no packages were found with the given configuration. " +
192                     "check your <sources /> configuration");
193 
194             final boolean isConfiguredToThrowExceptionWhenNoPackagesFound
195                     = configuration.shouldThrowExceptionWhenNoPackages();
196 
197             log.debug("throw exception when no packages? "
198                     + isConfiguredToThrowExceptionWhenNoPackagesFound);
199 
200             if (isConfiguredToThrowExceptionWhenNoPackagesFound) {
201                 log.debug("throwing RuntimeException because no packages " +
202                         "were found");
203 
204                 throw new NoPackagesFoundException("no packages were found " +
205                         "with the given configuration. check your <sources /> " +
206                         "configuration");
207             }
208         } else {
209             log.debug("jdepend found " + packages.size()
210                     + " to analyze for cyclic redundancies");
211         }
212     }
213 
214 
215     /***
216      * <p>All of the packages that were, or will be analyzed by the JDepend
217      * instance, given the source paths that it knows about.</p>
218      *
219      * @return Collection
220      */
221     Collection<JavaPackage> getPackages() {
222         return this.packages;
223     }
224 
225 
226     /***
227      * <p>Test the given layer (a package, but package is java keyword) against
228      * the given Rules</p>
229      *
230      * @param layer String the package to test
231      * @param violations Collection of packages defining which packages the
232      * layer may not use
233      * @throws DependencyConstraintException when a rule is broken
234      * @throws CyclicRedundancyException when cyclic redundancy is found
235      */
236     void testLayeringValid(final JPackage layer,
237                            final Collection<JPackage> violations)
238             throws DependencyConstraintException, CyclicRedundancyException {
239 
240         final Collection<JavaPackage> analyzedPackages = jdepend.analyze();
241 
242         log.debug("checking how many packages were found by JDepend");
243 
244         if (analyzedPackages.isEmpty()) {
245 
246             log.warn("no packages were found with the given configuration. " +
247                     "check your <sources />");
248 
249             final boolean isConfiguredToThrowExceptionWhenNoPackagesFound
250                     = this.configuration.shouldThrowExceptionWhenNoPackages();
251 
252             log.debug("throw exception when no packages? "
253                     + isConfiguredToThrowExceptionWhenNoPackagesFound);
254 
255             if (isConfiguredToThrowExceptionWhenNoPackagesFound) {
256 
257                 log.debug("throwing CyclicRedundancyException");
258 
259                 final String message = "cyclic redundancy does exist";
260                 throw new CyclicRedundancyException(message);
261             }
262 
263         } else {
264 
265             log.debug("jdepend found " + analyzedPackages.size()
266                     + " to analyze for dependency architecture");
267         }
268 
269         for (final JavaPackage analyzedPackage : analyzedPackages) {
270 
271             final JPackage javaPackage = new JPackage(
272                     analyzedPackage.getName());
273 
274             log.debug("checking dependencies on package " + javaPackage);
275 
276             if (layer.matches(javaPackage))
277                 testEfferentsValid(violations, analyzedPackage);
278         }
279     }
280 
281 
282     /***
283      * <p>Test a given layer (java package) against a Collection of
284      * <code>Rules</code></p>
285      *
286      * @param violations Collection of rules defining which packages the given
287      * package may not depend upon
288      * @param jPackage JavaPackage
289      * @throws DependencyConstraintException when a rule is broken
290      */
291     private void testEfferentsValid(
292             final Collection<JPackage> violations,
293             final JavaPackage jPackage)
294             throws DependencyConstraintException {
295 
296         final Collection<JavaPackage> efferents = jPackage.getEfferents();
297 
298         for (final JavaPackage efferent : efferents) {
299 
300             final String analyzedPackageName = jPackage.getName();
301             final JPackage efferentJPackage = new JPackage(efferent.getName());
302 
303             for (final JPackage violation : violations) {
304 
305                 if (violation.matches(efferentJPackage)) {
306 
307                     final String message = analyzedPackageName
308                             + " is not allowed to depend upon "
309                             + efferent.getName();
310 
311                     log.error(message);
312 
313                     throw new DependencyConstraintException(message);
314                 }
315             }
316         }
317     }
318 }