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
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
112 jdepend = new JDepend();
113
114
115 final Collection<SourceDirectory> sources
116 = this.configuration.getSources();
117
118
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
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 }