001    /*
002     * SonarQube, open source software quality management tool.
003     * Copyright (C) 2008-2013 SonarSource
004     * mailto:contact AT sonarsource DOT com
005     *
006     * SonarQube 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     * SonarQube 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 License
017     * along with this program; if not, write to the Free Software Foundation,
018     * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
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      public static final String BUILD_DIR_PROPERTY = "sonar.buildDir";
046    
047      private static final char SEPARATOR = ',';
048    
049      private File baseDir, workDir, buildDir;
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 ProjectDefinition setBuildDir(File d) {
093        this.buildDir = d;
094        return this;
095      }
096    
097      public File getBuildDir() {
098        return buildDir;
099      }
100    
101      public Properties getProperties() {
102        return properties;
103      }
104    
105      /**
106       * Copies specified properties into this object.
107       *
108       * @since 2.12
109       */
110      public ProjectDefinition setProperties(Properties properties) {
111        this.properties.putAll(properties);
112        return this;
113      }
114    
115      public ProjectDefinition setProperty(String key, String value) {
116        properties.setProperty(key, value);
117        return this;
118      }
119    
120      public ProjectDefinition setKey(String key) {
121        properties.setProperty(CoreProperties.PROJECT_KEY_PROPERTY, key);
122        return this;
123      }
124    
125      public ProjectDefinition setVersion(String s) {
126        properties.setProperty(CoreProperties.PROJECT_VERSION_PROPERTY, StringUtils.defaultString(s));
127        return this;
128      }
129    
130      public ProjectDefinition setName(String s) {
131        properties.setProperty(CoreProperties.PROJECT_NAME_PROPERTY, StringUtils.defaultString(s));
132        return this;
133      }
134    
135      public ProjectDefinition setDescription(String s) {
136        properties.setProperty(CoreProperties.PROJECT_DESCRIPTION_PROPERTY, StringUtils.defaultString(s));
137        return this;
138      }
139    
140      public String getKey() {
141        return properties.getProperty(CoreProperties.PROJECT_KEY_PROPERTY);
142      }
143    
144      public String getVersion() {
145        return properties.getProperty(CoreProperties.PROJECT_VERSION_PROPERTY);
146      }
147    
148      public String getName() {
149        String name = properties.getProperty(CoreProperties.PROJECT_NAME_PROPERTY);
150        if (StringUtils.isBlank(name)) {
151          name = "Unnamed - " + getKey();
152        }
153        return name;
154      }
155    
156      public String getDescription() {
157        return properties.getProperty(CoreProperties.PROJECT_DESCRIPTION_PROPERTY);
158      }
159    
160      private void appendProperty(String key, String value) {
161        String newValue = properties.getProperty(key, "") + SEPARATOR + value;
162        properties.put(key, newValue);
163      }
164    
165      public List<String> getSourceDirs() {
166        String sources = properties.getProperty(SOURCE_DIRS_PROPERTY, "");
167        return trim(StringUtils.split(sources, SEPARATOR));
168      }
169    
170      /**
171       * @param paths paths to directory with main sources.
172       *              They can be absolute or relative to project base directory.
173       */
174      public ProjectDefinition addSourceDirs(String... paths) {
175        for (String path : paths) {
176          appendProperty(SOURCE_DIRS_PROPERTY, path);
177        }
178        return this;
179      }
180    
181      public ProjectDefinition addSourceDirs(File... dirs) {
182        for (File dir : dirs) {
183          addSourceDirs(dir.getAbsolutePath());
184        }
185        return this;
186      }
187    
188      public ProjectDefinition resetSourceDirs() {
189        properties.remove(SOURCE_DIRS_PROPERTY);
190        return this;
191      }
192    
193      public ProjectDefinition setSourceDirs(String... paths) {
194        resetSourceDirs();
195        return addSourceDirs(paths);
196      }
197    
198      public ProjectDefinition setSourceDirs(File... dirs) {
199        resetSourceDirs();
200        for (File dir : dirs) {
201          addSourceDirs(dir.getAbsolutePath());
202        }
203        return this;
204      }
205    
206      /**
207       * Adding source files is possible only if no source directories have been set.
208       * Absolute path or relative path from project base dir.
209       */
210      public ProjectDefinition addSourceFiles(String... paths) {
211        for (String path : paths) {
212          appendProperty(SOURCE_FILES_PROPERTY, path);
213        }
214        return this;
215      }
216    
217      /**
218       * Adding source files is possible only if no source directories have been set.
219       */
220      public ProjectDefinition addSourceFiles(File... files) {
221        for (File file : files) {
222          addSourceFiles(file.getAbsolutePath());
223        }
224        return this;
225      }
226    
227      public List<String> getSourceFiles() {
228        String sources = properties.getProperty(SOURCE_FILES_PROPERTY, "");
229        return trim(StringUtils.split(sources, SEPARATOR));
230      }
231    
232      public List<String> getTestDirs() {
233        String sources = properties.getProperty(TEST_DIRS_PROPERTY, "");
234        return trim(StringUtils.split(sources, SEPARATOR));
235      }
236    
237      /**
238       * @param paths path to directory with test sources.
239       *              It can be absolute or relative to project directory.
240       */
241      public ProjectDefinition addTestDirs(String... paths) {
242        for (String path : paths) {
243          appendProperty(TEST_DIRS_PROPERTY, path);
244        }
245        return this;
246      }
247    
248      public ProjectDefinition addTestDirs(File... dirs) {
249        for (File dir : dirs) {
250          addTestDirs(dir.getAbsolutePath());
251        }
252        return this;
253      }
254    
255      public ProjectDefinition setTestDirs(String... paths) {
256        resetTestDirs();
257        return addTestDirs(paths);
258      }
259    
260      public ProjectDefinition setTestDirs(File... dirs) {
261        resetTestDirs();
262        for (File dir : dirs) {
263          addTestDirs(dir.getAbsolutePath());
264        }
265        return this;
266      }
267    
268      public ProjectDefinition resetTestDirs() {
269        properties.remove(TEST_DIRS_PROPERTY);
270        return this;
271      }
272    
273      /**
274       * Adding source files is possible only if no source directories have been set.
275       * Absolute path or relative path from project base dir.
276       */
277      public ProjectDefinition addTestFiles(String... paths) {
278        for (String path : paths) {
279          appendProperty(TEST_FILES_PROPERTY, path);
280        }
281        return this;
282      }
283    
284      /**
285       * Adding source files is possible only if no source directories have been set.
286       */
287      public ProjectDefinition addTestFiles(File... files) {
288        for (File file : files) {
289          addTestFiles(file.getAbsolutePath());
290        }
291        return this;
292      }
293    
294      public List<String> getTestFiles() {
295        String sources = properties.getProperty(TEST_FILES_PROPERTY, "");
296        return trim(StringUtils.split(sources, SEPARATOR));
297      }
298    
299      public List<String> getBinaries() {
300        String sources = properties.getProperty(BINARIES_PROPERTY, "");
301        return trim(StringUtils.split(sources, SEPARATOR));
302      }
303    
304      /**
305       * @param path path to directory with compiled source. In case of Java this is directory with class files.
306       *             It can be absolute or relative to project directory.
307       *             TODO currently Sonar supports only one such directory due to dependency on MavenProject
308       */
309      public ProjectDefinition addBinaryDir(String path) {
310        appendProperty(BINARIES_PROPERTY, path);
311        return this;
312      }
313    
314      public ProjectDefinition addBinaryDir(File f) {
315        return addBinaryDir(f.getAbsolutePath());
316      }
317    
318      public List<String> getLibraries() {
319        String sources = properties.getProperty(LIBRARIES_PROPERTY, "");
320        return trim(StringUtils.split(sources, SEPARATOR));
321      }
322    
323      /**
324       * @param path path to file with third-party library. In case of Java this is path to jar file.
325       *             It can be absolute or relative to project directory.
326       */
327      public void addLibrary(String path) {
328        appendProperty(LIBRARIES_PROPERTY, path);
329      }
330    
331      /**
332       * Adds an extension, which would be available in PicoContainer during analysis of this project.
333       *
334       * @since 2.8
335       */
336      public ProjectDefinition addContainerExtension(Object extension) {
337        containerExtensions.add(extension);
338        return this;
339      }
340    
341      /**
342       * @since 2.8
343       */
344      public List<Object> getContainerExtensions() {
345        return containerExtensions;
346      }
347    
348      /**
349       * @since 2.8
350       */
351      public ProjectDefinition addSubProject(ProjectDefinition child) {
352        subProjects.add(child);
353        child.setParent(this);
354        return this;
355      }
356    
357      public ProjectDefinition getParent() {
358        return parent;
359      }
360    
361      public void remove() {
362        if (parent != null) {
363          parent.subProjects.remove(this);
364          parent = null;
365          subProjects.clear();
366        }
367      }
368    
369      private void setParent(ProjectDefinition parent) {
370        this.parent = parent;
371      }
372    
373      /**
374       * @since 2.8
375       */
376      public List<ProjectDefinition> getSubProjects() {
377        return subProjects;
378      }
379    
380      private static List<String> trim(String[] strings) {
381        List<String> result = Lists.newArrayList();
382        for (String s : strings) {
383          result.add(StringUtils.trim(s));
384        }
385        return result;
386      }
387    
388      @Override
389      public boolean equals(Object o) {
390        if (this == o) {
391          return true;
392        }
393        if (o == null || getClass() != o.getClass()) {
394          return false;
395        }
396        ProjectDefinition that = (ProjectDefinition) o;
397        String key = getKey();
398        if (key != null ? !key.equals(that.getKey()) : that.getKey() != null) {
399          return false;
400        }
401    
402        return true;
403      }
404    
405      @Override
406      public int hashCode() {
407        String key = getKey();
408        return key != null ? key.hashCode() : 0;
409      }
410    }