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