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