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