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.StringUtils;
024import org.sonar.api.CoreProperties;
025
026import javax.annotation.CheckForNull;
027import javax.annotation.Nullable;
028
029import java.io.File;
030import java.util.List;
031import 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 */
040public 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}