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