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