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 SOURCES_PROPERTY = "sonar.sources";
043      /**
044       * @deprecated since 4.5 use {@link #SOURCES_PROPERTY}
045       */
046      @Deprecated
047      public static final String SOURCE_DIRS_PROPERTY = SOURCES_PROPERTY;
048      /**
049       * @deprecated since 4.5 use {@link #SOURCES_PROPERTY}
050       */
051      @Deprecated
052      public static final String SOURCE_FILES_PROPERTY = "sonar.sourceFiles";
053    
054      public static final String TESTS_PROPERTY = "sonar.tests";
055      /**
056       * @deprecated since 4.5 use {@link #TESTS_PROPERTY}
057       */
058      @Deprecated
059      public static final String TEST_DIRS_PROPERTY = TESTS_PROPERTY;
060      /**
061       * @deprecated since 4.5 use {@link #TESTS_PROPERTY}
062       */
063      @Deprecated
064      public static final String TEST_FILES_PROPERTY = "sonar.testFiles";
065      /**
066       * @deprecated since 4.5.1 use SonarQube Java specific API
067       */
068      @Deprecated
069      public static final String BINARIES_PROPERTY = "sonar.binaries";
070      /**
071       * @deprecated since 4.5.1 use SonarQube Java specific API
072       */
073      @Deprecated
074      public static final String LIBRARIES_PROPERTY = "sonar.libraries";
075      public static final String BUILD_DIR_PROPERTY = "sonar.buildDir";
076    
077      private static final char SEPARATOR = ',';
078    
079      private File baseDir, workDir, buildDir;
080      private Properties properties = new Properties();
081      private ProjectDefinition parent = null;
082      private List<ProjectDefinition> subProjects = Lists.newArrayList();
083      private List<Object> containerExtensions = Lists.newArrayList();
084    
085      private ProjectDefinition(Properties p) {
086        this.properties = p;
087      }
088    
089      /**
090       * @deprecated in 2.12, because it uses external object to represent internal state.
091       *             To ensure backward-compatibility with Ant task this method cannot clone properties,
092       *             so other callers must explicitly make clone of properties before passing into this method.
093       *             Thus better to use {@link #create()} with combination of other methods like {@link #setProperties(Properties)} and {@link #setProperty(String, String)}.
094       */
095      @Deprecated
096      public static ProjectDefinition create(Properties properties) {
097        return new ProjectDefinition(properties);
098      }
099    
100      public static ProjectDefinition create() {
101        return new ProjectDefinition(new Properties());
102      }
103    
104      public ProjectDefinition setBaseDir(File baseDir) {
105        this.baseDir = baseDir;
106        return this;
107      }
108    
109      public File getBaseDir() {
110        return baseDir;
111      }
112    
113      public ProjectDefinition setWorkDir(@Nullable File workDir) {
114        this.workDir = workDir;
115        return this;
116      }
117    
118      @CheckForNull
119      public File getWorkDir() {
120        return workDir;
121      }
122    
123      public ProjectDefinition setBuildDir(@Nullable File d) {
124        this.buildDir = d;
125        return this;
126      }
127    
128      @CheckForNull
129      public File getBuildDir() {
130        return buildDir;
131      }
132    
133      public Properties getProperties() {
134        return properties;
135      }
136    
137      /**
138       * Copies specified properties into this object.
139       *
140       * @since 2.12
141       */
142      public ProjectDefinition setProperties(Properties properties) {
143        this.properties.putAll(properties);
144        return this;
145      }
146    
147      public ProjectDefinition setProperty(String key, String value) {
148        properties.setProperty(key, value);
149        return this;
150      }
151    
152      public ProjectDefinition setKey(String key) {
153        properties.setProperty(CoreProperties.PROJECT_KEY_PROPERTY, key);
154        return this;
155      }
156    
157      public ProjectDefinition setVersion(String s) {
158        properties.setProperty(CoreProperties.PROJECT_VERSION_PROPERTY, StringUtils.defaultString(s));
159        return this;
160      }
161    
162      public ProjectDefinition setName(String s) {
163        properties.setProperty(CoreProperties.PROJECT_NAME_PROPERTY, StringUtils.defaultString(s));
164        return this;
165      }
166    
167      public ProjectDefinition setDescription(String s) {
168        properties.setProperty(CoreProperties.PROJECT_DESCRIPTION_PROPERTY, StringUtils.defaultString(s));
169        return this;
170      }
171    
172      public String getKey() {
173        return properties.getProperty(CoreProperties.PROJECT_KEY_PROPERTY);
174      }
175    
176      /**
177       * @since 4.5
178       */
179      public String getKeyWithBranch() {
180        String branch = properties.getProperty(CoreProperties.PROJECT_BRANCH_PROPERTY);
181        String projectKey = getKey();
182        if (StringUtils.isNotBlank(branch)) {
183          projectKey = String.format("%s:%s", projectKey, branch);
184        }
185        return projectKey;
186      }
187    
188      public String getVersion() {
189        return properties.getProperty(CoreProperties.PROJECT_VERSION_PROPERTY);
190      }
191    
192      public String getName() {
193        String name = properties.getProperty(CoreProperties.PROJECT_NAME_PROPERTY);
194        if (StringUtils.isBlank(name)) {
195          name = "Unnamed - " + getKey();
196        }
197        return name;
198      }
199    
200      public String getDescription() {
201        return properties.getProperty(CoreProperties.PROJECT_DESCRIPTION_PROPERTY);
202      }
203    
204      private void appendProperty(String key, String value) {
205        String current = properties.getProperty(key, "");
206        if (StringUtils.isBlank(current)) {
207          properties.put(key, value);
208        } else {
209          properties.put(key, current + SEPARATOR + value);
210        }
211      }
212    
213      /**
214       * @return Source files and folders.
215       */
216      public List<String> sources() {
217        String sources = properties.getProperty(SOURCES_PROPERTY, "");
218        return trim(StringUtils.split(sources, SEPARATOR));
219      }
220    
221      /**
222       * @deprecated since 4.5 use {@link #sources()}
223       */
224      @Deprecated
225      public List<String> getSourceDirs() {
226        return sources();
227      }
228    
229      /**
230       * @param paths paths to file or directory with main sources.
231       *              They can be absolute or relative to project base directory.
232       */
233      public ProjectDefinition addSources(String... paths) {
234        for (String path : paths) {
235          appendProperty(SOURCES_PROPERTY, path);
236        }
237        return this;
238      }
239    
240      /**
241       * @deprecated since 4.5 use {@link #addSources(String...)}
242       */
243      @Deprecated
244      public ProjectDefinition addSourceDirs(String... paths) {
245        return addSources(paths);
246      }
247    
248      public ProjectDefinition addSources(File... fileOrDirs) {
249        for (File fileOrDir : fileOrDirs) {
250          addSources(fileOrDir.getAbsolutePath());
251        }
252        return this;
253      }
254    
255      /**
256       * @deprecated since 4.5 use {@link #addSources(File...)}
257       */
258      @Deprecated
259      public ProjectDefinition addSourceDirs(File... dirs) {
260        return addSources(dirs);
261      }
262    
263      public ProjectDefinition resetSources() {
264        properties.remove(SOURCES_PROPERTY);
265        return this;
266      }
267    
268      /**
269       * @deprecated since 4.5 use {@link #resetSources()}
270       */
271      @Deprecated
272      public ProjectDefinition resetSourceDirs() {
273        return resetSources();
274      }
275    
276      public ProjectDefinition setSources(String... paths) {
277        resetSources();
278        return addSources(paths);
279      }
280    
281      /**
282       * @deprecated since 4.5 use {@link #setSources(String...)}
283       */
284      @Deprecated
285      public ProjectDefinition setSourceDirs(String... paths) {
286        return setSources(paths);
287      }
288    
289      public ProjectDefinition setSources(File... filesOrDirs) {
290        resetSources();
291        for (File fileOrDir : filesOrDirs) {
292          addSources(fileOrDir.getAbsolutePath());
293        }
294        return this;
295      }
296    
297      /**
298       * @deprecated since 4.5 use {@link #setSources(File...)}
299       */
300      @Deprecated
301      public ProjectDefinition setSourceDirs(File... dirs) {
302        resetSourceDirs();
303        for (File dir : dirs) {
304          addSourceDirs(dir.getAbsolutePath());
305        }
306        return this;
307      }
308    
309      /**
310       * @deprecated since 4.5 use {@link #addSources(File...)}
311       */
312      @Deprecated
313      public ProjectDefinition addSourceFiles(String... paths) {
314        // Hack for visual studio project builder that used to add baseDir first as source dir
315        List<String> sourceDirs = getSourceDirs();
316        if (sourceDirs.size() == 1 && isDirectory(sourceDirs.get(0))) {
317          resetSources();
318        }
319        return addSources(paths);
320      }
321    
322      /**
323       * @deprecated since 4.5 use {@link #addSources(File...)}
324       */
325      @Deprecated
326      public ProjectDefinition addSourceFiles(File... files) {
327        // Hack for visual studio project builder that used to add baseDir first as source dir
328        List<String> sourceDirs = getSourceDirs();
329        if (sourceDirs.size() == 1 && isDirectory(sourceDirs.get(0))) {
330          resetSources();
331        }
332        return addSources(files);
333      }
334    
335      /**
336       * @deprecated since 4.5 use {@link #sources()}
337       */
338      @Deprecated
339      public List<String> getSourceFiles() {
340        return sources();
341      }
342    
343      public List<String> tests() {
344        String sources = properties.getProperty(TESTS_PROPERTY, "");
345        return trim(StringUtils.split(sources, SEPARATOR));
346      }
347    
348      /**
349       * @deprecated since 4.5 use {@link #tests()}
350       */
351      @Deprecated
352      public List<String> getTestDirs() {
353        return tests();
354      }
355    
356      /**
357       * @param paths path to files or directories with test sources.
358       *              It can be absolute or relative to project directory.
359       */
360      public ProjectDefinition addTests(String... paths) {
361        for (String path : paths) {
362          appendProperty(TESTS_PROPERTY, path);
363        }
364        return this;
365      }
366    
367      /**
368       * @deprecated since 4.5 use {@link #addTests(String...)}
369       */
370      @Deprecated
371      public ProjectDefinition addTestDirs(String... paths) {
372        return addTests(paths);
373      }
374    
375      public ProjectDefinition addTests(File... fileOrDirs) {
376        for (File fileOrDir : fileOrDirs) {
377          addTests(fileOrDir.getAbsolutePath());
378        }
379        return this;
380      }
381    
382      /**
383       * @deprecated since 4.5 use {@link #addTests(File...)}
384       */
385      @Deprecated
386      public ProjectDefinition addTestDirs(File... dirs) {
387        return addTests(dirs);
388      }
389    
390      public ProjectDefinition setTests(String... paths) {
391        resetTests();
392        return addTests(paths);
393      }
394    
395      /**
396       * @deprecated since 4.5 use {@link #setTests(String...)}
397       */
398      @Deprecated
399      public ProjectDefinition setTestDirs(String... paths) {
400        return setTests(paths);
401      }
402    
403      public ProjectDefinition setTests(File... fileOrDirs) {
404        resetTests();
405        for (File dir : fileOrDirs) {
406          addTests(dir.getAbsolutePath());
407        }
408        return this;
409      }
410    
411      /**
412       * @deprecated since 4.5 use {@link #setTests(File...)}
413       */
414      @Deprecated
415      public ProjectDefinition setTestDirs(File... dirs) {
416        return setTests(dirs);
417      }
418    
419      public ProjectDefinition resetTests() {
420        properties.remove(TESTS_PROPERTY);
421        return this;
422      }
423    
424      /**
425       * @deprecated since 4.5 use {@link #resetTests()}
426       */
427      @Deprecated
428      public ProjectDefinition resetTestDirs() {
429        return resetTests();
430      }
431    
432      /**
433       * @deprecated since 4.5 use {@link #addTests(String...)}
434       */
435      @Deprecated
436      public ProjectDefinition addTestFiles(String... paths) {
437        // Hack for visual studio project builder that used to add baseDir first as test dir
438        List<String> testDirs = getTestDirs();
439        if (testDirs.size() == 1 && isDirectory(testDirs.get(0))) {
440          resetTests();
441        }
442        return addTests(paths);
443      }
444    
445      private boolean isDirectory(String relativeOrAbsoluteDir) {
446        File file = new File(relativeOrAbsoluteDir);
447        if (!file.isAbsolute()) {
448          file = new File(baseDir, relativeOrAbsoluteDir);
449        }
450        return file.isDirectory();
451      }
452    
453      /**
454       * @deprecated since 4.5 use {@link #addTests(File...)}
455       */
456      @Deprecated
457      public ProjectDefinition addTestFiles(File... files) {
458        // Hack for visual studio project builder that used to add baseDir first as test dir
459        List<String> testDirs = getTestDirs();
460        if (testDirs.size() == 1 && isDirectory(testDirs.get(0))) {
461          resetTests();
462        }
463        return addTests(files);
464      }
465    
466      /**
467       * @deprecated since 4.5 use {@link #tests()}
468       */
469      @Deprecated
470      public List<String> getTestFiles() {
471        return tests();
472      }
473    
474      /**
475       * @deprecated since 4.5.1 use SonarQube Java specific API
476       */
477      @Deprecated
478      public List<String> getBinaries() {
479        String sources = properties.getProperty(BINARIES_PROPERTY, "");
480        return trim(StringUtils.split(sources, SEPARATOR));
481      }
482    
483      /**
484       * @param path path to directory with compiled source. In case of Java this is directory with class files.
485       *             It can be absolute or relative to project directory.
486       * @deprecated since 4.5.1 use SonarQube Java specific API
487       */
488      @Deprecated
489      public ProjectDefinition addBinaryDir(String path) {
490        appendProperty(BINARIES_PROPERTY, path);
491        return this;
492      }
493    
494      /**
495       * @deprecated since 4.5.1 use SonarQube Java specific API
496       */
497      @Deprecated
498      public ProjectDefinition addBinaryDir(File f) {
499        return addBinaryDir(f.getAbsolutePath());
500      }
501    
502      /**
503       * @deprecated since 4.5.1 use SonarQube Java specific API
504       */
505      @Deprecated
506      public List<String> getLibraries() {
507        String sources = properties.getProperty(LIBRARIES_PROPERTY, "");
508        return trim(StringUtils.split(sources, SEPARATOR));
509      }
510    
511      /**
512       * @param path path to file with third-party library. In case of Java this is path to jar file.
513       *             It can be absolute or relative to project directory.
514       * @deprecated since 4.5.1 use SonarQube Java specific API
515       */
516      @Deprecated
517      public void addLibrary(String path) {
518        appendProperty(LIBRARIES_PROPERTY, path);
519      }
520    
521      /**
522       * Adds an extension, which would be available in PicoContainer during analysis of this project.
523       *
524       * @since 2.8
525       */
526      public ProjectDefinition addContainerExtension(Object extension) {
527        containerExtensions.add(extension);
528        return this;
529      }
530    
531      /**
532       * @since 2.8
533       */
534      public List<Object> getContainerExtensions() {
535        return containerExtensions;
536      }
537    
538      /**
539       * @since 2.8
540       */
541      public ProjectDefinition addSubProject(ProjectDefinition child) {
542        subProjects.add(child);
543        child.setParent(this);
544        return this;
545      }
546    
547      public ProjectDefinition getParent() {
548        return parent;
549      }
550    
551      public void remove() {
552        if (parent != null) {
553          parent.subProjects.remove(this);
554          parent = null;
555          subProjects.clear();
556        }
557      }
558    
559      private void setParent(ProjectDefinition parent) {
560        this.parent = parent;
561      }
562    
563      /**
564       * @since 2.8
565       */
566      public List<ProjectDefinition> getSubProjects() {
567        return subProjects;
568      }
569    
570      private static List<String> trim(String[] strings) {
571        List<String> result = Lists.newArrayList();
572        for (String s : strings) {
573          result.add(StringUtils.trim(s));
574        }
575        return result;
576      }
577    
578      @Override
579      public boolean equals(Object o) {
580        if (this == o) {
581          return true;
582        }
583        if (o == null || getClass() != o.getClass()) {
584          return false;
585        }
586        ProjectDefinition that = (ProjectDefinition) o;
587        String key = getKey();
588        if (key != null ? !key.equals(that.getKey()) : that.getKey() != null) {
589          return false;
590        }
591    
592        return true;
593      }
594    
595      @Override
596      public int hashCode() {
597        String key = getKey();
598        return key != null ? key.hashCode() : 0;
599      }
600    }