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