001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2008-2012 SonarSource
004     * mailto:contact AT sonarsource DOT com
005     *
006     * Sonar is free software; you can redistribute it and/or
007     * modify it under the terms of the GNU Lesser General Public
008     * License as published by the Free Software Foundation; either
009     * version 3 of the License, or (at your option) any later version.
010     *
011     * Sonar is distributed in the hope that it will be useful,
012     * but WITHOUT ANY WARRANTY; without even the implied warranty of
013     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014     * Lesser General Public License for more details.
015     *
016     * You should have received a copy of the GNU Lesser General Public
017     * License along with Sonar; if not, write to the Free Software
018     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
019     */
020    package org.sonar.api.batch.bootstrap;
021    
022    import com.google.common.collect.Lists;
023    import org.apache.commons.lang.StringUtils;
024    import org.sonar.api.CoreProperties;
025    
026    import java.io.File;
027    import java.util.List;
028    import java.util.Properties;
029    
030    /**
031     * Defines project metadata (key, name, source directories, ...). It's generally used by the
032     * {@link org.sonar.api.batch.bootstrap.ProjectBuilder extension point} and must not be used
033     * by other standard extensions.
034     *
035     * @since 2.9
036     */
037    public class ProjectDefinition {
038    
039      public static final String SOURCE_DIRS_PROPERTY = "sonar.sources";
040      public static final String SOURCE_FILES_PROPERTY = "sonar.sourceFiles";
041      public static final String TEST_DIRS_PROPERTY = "sonar.tests";
042      public static final String TEST_FILES_PROPERTY = "sonar.testFiles";
043      public static final String BINARIES_PROPERTY = "sonar.binaries";
044      public static final String LIBRARIES_PROPERTY = "sonar.libraries";
045    
046      private static final char SEPARATOR = ',';
047    
048      private File baseDir;
049      private File workDir;
050      private Properties properties = new Properties();
051      private ProjectDefinition parent = null;
052      private List<ProjectDefinition> subProjects = Lists.newArrayList();
053      private List<Object> containerExtensions = Lists.newArrayList();
054    
055      private ProjectDefinition(Properties p) {
056        this.properties = p;
057      }
058    
059      /**
060       * @deprecated in 2.12, because it uses external object to represent internal state.
061       *             To ensure backward-compatibility with Ant task this method cannot clone properties,
062       *             so other callers must explicitly make clone of properties before passing into this method.
063       *             Thus better to use {@link #create()} with combination of other methods like {@link #setProperties(Properties)} and {@link #setProperty(String, String)}.
064       */
065      @Deprecated
066      public static ProjectDefinition create(Properties properties) {
067        return new ProjectDefinition(properties);
068      }
069    
070      public static ProjectDefinition create() {
071        return new ProjectDefinition(new Properties());
072      }
073    
074      public ProjectDefinition setBaseDir(File baseDir) {
075        this.baseDir = baseDir;
076        return this;
077      }
078    
079      public File getBaseDir() {
080        return baseDir;
081      }
082    
083      public ProjectDefinition setWorkDir(File workDir) {
084        this.workDir = workDir;
085        return this;
086      }
087    
088      public File getWorkDir() {
089        return workDir;
090      }
091    
092      public Properties getProperties() {
093        return properties;
094      }
095    
096      /**
097       * Copies specified properties into this object.
098       *
099       * @since 2.12
100       */
101      public ProjectDefinition setProperties(Properties properties) {
102        this.properties.putAll(properties);
103        return this;
104      }
105    
106      public ProjectDefinition setProperty(String key, String value) {
107        properties.setProperty(key, value);
108        return this;
109      }
110    
111      public ProjectDefinition setKey(String key) {
112        properties.setProperty(CoreProperties.PROJECT_KEY_PROPERTY, key);
113        return this;
114      }
115    
116      public ProjectDefinition setVersion(String s) {
117        properties.setProperty(CoreProperties.PROJECT_VERSION_PROPERTY, StringUtils.defaultString(s));
118        return this;
119      }
120    
121      public ProjectDefinition setName(String s) {
122        properties.setProperty(CoreProperties.PROJECT_NAME_PROPERTY, StringUtils.defaultString(s));
123        return this;
124      }
125    
126      public ProjectDefinition setDescription(String s) {
127        properties.setProperty(CoreProperties.PROJECT_DESCRIPTION_PROPERTY, StringUtils.defaultString(s));
128        return this;
129      }
130    
131      public String getKey() {
132        return properties.getProperty(CoreProperties.PROJECT_KEY_PROPERTY);
133      }
134    
135      public String getVersion() {
136        return properties.getProperty(CoreProperties.PROJECT_VERSION_PROPERTY);
137      }
138    
139      public String getName() {
140        String name = properties.getProperty(CoreProperties.PROJECT_NAME_PROPERTY);
141        if (StringUtils.isBlank(name)) {
142          name = "Unnamed - " + getKey();
143        }
144        return name;
145      }
146    
147      public String getDescription() {
148        return properties.getProperty(CoreProperties.PROJECT_DESCRIPTION_PROPERTY);
149      }
150    
151      private void appendProperty(String key, String value) {
152        String newValue = properties.getProperty(key, "") + SEPARATOR + value;
153        properties.put(key, newValue);
154      }
155    
156      public List<String> getSourceDirs() {
157        String sources = properties.getProperty(SOURCE_DIRS_PROPERTY, "");
158        return trim(StringUtils.split(sources, SEPARATOR));
159      }
160    
161      /**
162       * @param paths paths to directory with main sources.
163       *              They can be absolute or relative to project base directory.
164       */
165      public ProjectDefinition addSourceDirs(String... paths) {
166        for (String path : paths) {
167          appendProperty(SOURCE_DIRS_PROPERTY, path);
168        }
169        return this;
170      }
171    
172      public ProjectDefinition addSourceDirs(File... dirs) {
173        for (File dir : dirs) {
174          addSourceDirs(dir.getAbsolutePath());
175        }
176        return this;
177      }
178    
179      public ProjectDefinition resetSourceDirs() {
180        properties.remove(SOURCE_DIRS_PROPERTY);
181        return this;
182      }
183    
184      public ProjectDefinition setSourceDirs(String... paths) {
185        resetSourceDirs();
186        return addSourceDirs(paths);
187      }
188    
189      public ProjectDefinition setSourceDirs(File... dirs) {
190        resetSourceDirs();
191        for (File dir : dirs) {
192          addSourceDirs(dir.getAbsolutePath());
193        }
194        return this;
195      }
196    
197      /**
198       * Adding source files is possible only if no source directories have been set.
199       * Absolute path or relative path from project base dir.
200       */
201      public ProjectDefinition addSourceFiles(String... paths) {
202        for (String path : paths) {
203          appendProperty(SOURCE_FILES_PROPERTY, path);
204        }
205        return this;
206      }
207    
208      /**
209       * Adding source files is possible only if no source directories have been set.
210       */
211      public ProjectDefinition addSourceFiles(File... files) {
212        for (File file : files) {
213          addSourceFiles(file.getAbsolutePath());
214        }
215        return this;
216      }
217    
218      public List<String> getSourceFiles() {
219        String sources = properties.getProperty(SOURCE_FILES_PROPERTY, "");
220        return trim(StringUtils.split(sources, SEPARATOR));
221      }
222    
223      public List<String> getTestDirs() {
224        String sources = properties.getProperty(TEST_DIRS_PROPERTY, "");
225        return trim(StringUtils.split(sources, SEPARATOR));
226      }
227    
228      /**
229       * @param paths path to directory with test sources.
230       *              It can be absolute or relative to project directory.
231       */
232      public ProjectDefinition addTestDirs(String... paths) {
233        for (String path : paths) {
234          appendProperty(TEST_DIRS_PROPERTY, path);
235        }
236        return this;
237      }
238    
239      public ProjectDefinition addTestDirs(File... dirs) {
240        for (File dir : dirs) {
241          addTestDirs(dir.getAbsolutePath());
242        }
243        return this;
244      }
245    
246      public ProjectDefinition setTestDirs(String... paths) {
247        resetTestDirs();
248        return addTestDirs(paths);
249      }
250    
251      public ProjectDefinition setTestDirs(File... dirs) {
252        resetTestDirs();
253        for (File dir : dirs) {
254          addTestDirs(dir.getAbsolutePath());
255        }
256        return this;
257      }
258    
259      public ProjectDefinition resetTestDirs() {
260        properties.remove(TEST_DIRS_PROPERTY);
261        return this;
262      }
263    
264      /**
265       * Adding source files is possible only if no source directories have been set.
266       * Absolute path or relative path from project base dir.
267       */
268      public ProjectDefinition addTestFiles(String... paths) {
269        for (String path : paths) {
270          appendProperty(TEST_FILES_PROPERTY, path);
271        }
272        return this;
273      }
274    
275      /**
276       * Adding source files is possible only if no source directories have been set.
277       */
278      public ProjectDefinition addTestFiles(File... files) {
279        for (File file : files) {
280          addTestFiles(file.getAbsolutePath());
281        }
282        return this;
283      }
284    
285      public List<String> getTestFiles() {
286        String sources = properties.getProperty(TEST_FILES_PROPERTY, "");
287        return trim(StringUtils.split(sources, SEPARATOR));
288      }
289    
290      public List<String> getBinaries() {
291        String sources = properties.getProperty(BINARIES_PROPERTY, "");
292        return trim(StringUtils.split(sources, SEPARATOR));
293      }
294    
295      /**
296       * @param path path to directory with compiled source. In case of Java this is directory with class files.
297       *             It can be absolute or relative to project directory.
298       * @TODO currently Sonar supports only one such directory due to dependency on MavenProject
299       */
300      public ProjectDefinition addBinaryDir(String path) {
301        appendProperty(BINARIES_PROPERTY, path);
302        return this;
303      }
304    
305      public ProjectDefinition addBinaryDir(File f) {
306        return addBinaryDir(f.getAbsolutePath());
307      }
308    
309      public List<String> getLibraries() {
310        String sources = properties.getProperty(LIBRARIES_PROPERTY, "");
311        return trim(StringUtils.split(sources, SEPARATOR));
312      }
313    
314      /**
315       * @param path path to file with third-party library. In case of Java this is path to jar file.
316       *             It can be absolute or relative to project directory.
317       */
318      public void addLibrary(String path) {
319        appendProperty(LIBRARIES_PROPERTY, path);
320      }
321    
322      /**
323       * Adds an extension, which would be available in PicoContainer during analysis of this project.
324       *
325       * @since 2.8
326       */
327      public ProjectDefinition addContainerExtension(Object extension) {
328        containerExtensions.add(extension);
329        return this;
330      }
331    
332      /**
333       * @since 2.8
334       */
335      public List<Object> getContainerExtensions() {
336        return containerExtensions;
337      }
338    
339      /**
340       * @since 2.8
341       */
342      public ProjectDefinition addSubProject(ProjectDefinition child) {
343        subProjects.add(child);
344        child.setParent(this);
345        return this;
346      }
347    
348      public ProjectDefinition getParent() {
349        return parent;
350      }
351    
352      public void remove() {
353        if (parent != null) {
354          parent.subProjects.remove(this);
355          parent = null;
356          subProjects.clear();
357        }
358      }
359    
360      private void setParent(ProjectDefinition parent) {
361        this.parent = parent;
362      }
363    
364      /**
365       * @since 2.8
366       */
367      public List<ProjectDefinition> getSubProjects() {
368        return subProjects;
369      }
370    
371      private static List<String> trim(String[] strings) {
372        List<String> result = Lists.newArrayList();
373        for (String s : strings) {
374          result.add(StringUtils.trim(s));
375        }
376        return result;
377      }
378    
379      @Override
380      public boolean equals(Object o) {
381        if (this == o) {
382          return true;
383        }
384        if (o == null || getClass() != o.getClass()) {
385          return false;
386        }
387        ProjectDefinition that = (ProjectDefinition) o;
388        String key = getKey();
389        if (key != null ? !key.equals(that.getKey()) : that.getKey() != null) {
390          return false;
391        }
392    
393        return true;
394      }
395    
396      @Override
397      public int hashCode() {
398        String key = getKey();
399        return key != null ? key.hashCode() : 0;
400      }
401    }