001/*
002 * SonarQube, open source software quality management tool.
003 * Copyright (C) 2008-2013 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 java.io.File;
027import java.util.List;
028import java.util.Properties;
029
030/**
031 * Defines project metadata (key, name, source directories, ...). It's generally used by the
032 * {@link org.sonar.api.batch.bootstrap.ProjectBuilder extension point} and must not be used
033 * by other standard extensions.
034 *
035 * @since 2.9
036 */
037public class ProjectDefinition {
038
039  public static final String SOURCE_DIRS_PROPERTY = "sonar.sources";
040  public static final String SOURCE_FILES_PROPERTY = "sonar.sourceFiles";
041  public static final String TEST_DIRS_PROPERTY = "sonar.tests";
042  public static final String TEST_FILES_PROPERTY = "sonar.testFiles";
043  public static final String BINARIES_PROPERTY = "sonar.binaries";
044  public static final String LIBRARIES_PROPERTY = "sonar.libraries";
045  public static final String BUILD_DIR_PROPERTY = "sonar.buildDir";
046
047  private static final char SEPARATOR = ',';
048
049  private File baseDir, workDir, buildDir;
050  private Properties properties = new Properties();
051  private ProjectDefinition parent = null;
052  private List<ProjectDefinition> subProjects = Lists.newArrayList();
053  private List<Object> containerExtensions = Lists.newArrayList();
054
055  private ProjectDefinition(Properties p) {
056    this.properties = p;
057  }
058
059  /**
060   * @deprecated in 2.12, because it uses external object to represent internal state.
061   *             To ensure backward-compatibility with Ant task this method cannot clone properties,
062   *             so other callers must explicitly make clone of properties before passing into this method.
063   *             Thus better to use {@link #create()} with combination of other methods like {@link #setProperties(Properties)} and {@link #setProperty(String, String)}.
064   */
065  @Deprecated
066  public static ProjectDefinition create(Properties properties) {
067    return new ProjectDefinition(properties);
068  }
069
070  public static ProjectDefinition create() {
071    return new ProjectDefinition(new Properties());
072  }
073
074  public ProjectDefinition setBaseDir(File baseDir) {
075    this.baseDir = baseDir;
076    return this;
077  }
078
079  public File getBaseDir() {
080    return baseDir;
081  }
082
083  public ProjectDefinition setWorkDir(File workDir) {
084    this.workDir = workDir;
085    return this;
086  }
087
088  public File getWorkDir() {
089    return workDir;
090  }
091
092  public ProjectDefinition setBuildDir(File d) {
093    this.buildDir = d;
094    return this;
095  }
096
097  public File getBuildDir() {
098    return buildDir;
099  }
100
101  public Properties getProperties() {
102    return properties;
103  }
104
105  /**
106   * Copies specified properties into this object.
107   *
108   * @since 2.12
109   */
110  public ProjectDefinition setProperties(Properties properties) {
111    this.properties.putAll(properties);
112    return this;
113  }
114
115  public ProjectDefinition setProperty(String key, String value) {
116    properties.setProperty(key, value);
117    return this;
118  }
119
120  public ProjectDefinition setKey(String key) {
121    properties.setProperty(CoreProperties.PROJECT_KEY_PROPERTY, key);
122    return this;
123  }
124
125  public ProjectDefinition setVersion(String s) {
126    properties.setProperty(CoreProperties.PROJECT_VERSION_PROPERTY, StringUtils.defaultString(s));
127    return this;
128  }
129
130  public ProjectDefinition setName(String s) {
131    properties.setProperty(CoreProperties.PROJECT_NAME_PROPERTY, StringUtils.defaultString(s));
132    return this;
133  }
134
135  public ProjectDefinition setDescription(String s) {
136    properties.setProperty(CoreProperties.PROJECT_DESCRIPTION_PROPERTY, StringUtils.defaultString(s));
137    return this;
138  }
139
140  public String getKey() {
141    return properties.getProperty(CoreProperties.PROJECT_KEY_PROPERTY);
142  }
143
144  public String getVersion() {
145    return properties.getProperty(CoreProperties.PROJECT_VERSION_PROPERTY);
146  }
147
148  public String getName() {
149    String name = properties.getProperty(CoreProperties.PROJECT_NAME_PROPERTY);
150    if (StringUtils.isBlank(name)) {
151      name = "Unnamed - " + getKey();
152    }
153    return name;
154  }
155
156  public String getDescription() {
157    return properties.getProperty(CoreProperties.PROJECT_DESCRIPTION_PROPERTY);
158  }
159
160  private void appendProperty(String key, String value) {
161    String newValue = properties.getProperty(key, "") + SEPARATOR + value;
162    properties.put(key, newValue);
163  }
164
165  public List<String> getSourceDirs() {
166    String sources = properties.getProperty(SOURCE_DIRS_PROPERTY, "");
167    return trim(StringUtils.split(sources, SEPARATOR));
168  }
169
170  /**
171   * @param paths paths to directory with main sources.
172   *              They can be absolute or relative to project base directory.
173   */
174  public ProjectDefinition addSourceDirs(String... paths) {
175    for (String path : paths) {
176      appendProperty(SOURCE_DIRS_PROPERTY, path);
177    }
178    return this;
179  }
180
181  public ProjectDefinition addSourceDirs(File... dirs) {
182    for (File dir : dirs) {
183      addSourceDirs(dir.getAbsolutePath());
184    }
185    return this;
186  }
187
188  public ProjectDefinition resetSourceDirs() {
189    properties.remove(SOURCE_DIRS_PROPERTY);
190    return this;
191  }
192
193  public ProjectDefinition setSourceDirs(String... paths) {
194    resetSourceDirs();
195    return addSourceDirs(paths);
196  }
197
198  public ProjectDefinition setSourceDirs(File... dirs) {
199    resetSourceDirs();
200    for (File dir : dirs) {
201      addSourceDirs(dir.getAbsolutePath());
202    }
203    return this;
204  }
205
206  /**
207   * Adding source files is possible only if no source directories have been set.
208   * Absolute path or relative path from project base dir.
209   */
210  public ProjectDefinition addSourceFiles(String... paths) {
211    for (String path : paths) {
212      appendProperty(SOURCE_FILES_PROPERTY, path);
213    }
214    return this;
215  }
216
217  /**
218   * Adding source files is possible only if no source directories have been set.
219   */
220  public ProjectDefinition addSourceFiles(File... files) {
221    for (File file : files) {
222      addSourceFiles(file.getAbsolutePath());
223    }
224    return this;
225  }
226
227  public List<String> getSourceFiles() {
228    String sources = properties.getProperty(SOURCE_FILES_PROPERTY, "");
229    return trim(StringUtils.split(sources, SEPARATOR));
230  }
231
232  public List<String> getTestDirs() {
233    String sources = properties.getProperty(TEST_DIRS_PROPERTY, "");
234    return trim(StringUtils.split(sources, SEPARATOR));
235  }
236
237  /**
238   * @param paths path to directory with test sources.
239   *              It can be absolute or relative to project directory.
240   */
241  public ProjectDefinition addTestDirs(String... paths) {
242    for (String path : paths) {
243      appendProperty(TEST_DIRS_PROPERTY, path);
244    }
245    return this;
246  }
247
248  public ProjectDefinition addTestDirs(File... dirs) {
249    for (File dir : dirs) {
250      addTestDirs(dir.getAbsolutePath());
251    }
252    return this;
253  }
254
255  public ProjectDefinition setTestDirs(String... paths) {
256    resetTestDirs();
257    return addTestDirs(paths);
258  }
259
260  public ProjectDefinition setTestDirs(File... dirs) {
261    resetTestDirs();
262    for (File dir : dirs) {
263      addTestDirs(dir.getAbsolutePath());
264    }
265    return this;
266  }
267
268  public ProjectDefinition resetTestDirs() {
269    properties.remove(TEST_DIRS_PROPERTY);
270    return this;
271  }
272
273  /**
274   * Adding source files is possible only if no source directories have been set.
275   * Absolute path or relative path from project base dir.
276   */
277  public ProjectDefinition addTestFiles(String... paths) {
278    for (String path : paths) {
279      appendProperty(TEST_FILES_PROPERTY, path);
280    }
281    return this;
282  }
283
284  /**
285   * Adding source files is possible only if no source directories have been set.
286   */
287  public ProjectDefinition addTestFiles(File... files) {
288    for (File file : files) {
289      addTestFiles(file.getAbsolutePath());
290    }
291    return this;
292  }
293
294  public List<String> getTestFiles() {
295    String sources = properties.getProperty(TEST_FILES_PROPERTY, "");
296    return trim(StringUtils.split(sources, SEPARATOR));
297  }
298
299  public List<String> getBinaries() {
300    String sources = properties.getProperty(BINARIES_PROPERTY, "");
301    return trim(StringUtils.split(sources, SEPARATOR));
302  }
303
304  /**
305   * @param path path to directory with compiled source. In case of Java this is directory with class files.
306   *             It can be absolute or relative to project directory.
307   *             TODO currently Sonar supports only one such directory due to dependency on MavenProject
308   */
309  public ProjectDefinition addBinaryDir(String path) {
310    appendProperty(BINARIES_PROPERTY, path);
311    return this;
312  }
313
314  public ProjectDefinition addBinaryDir(File f) {
315    return addBinaryDir(f.getAbsolutePath());
316  }
317
318  public List<String> getLibraries() {
319    String sources = properties.getProperty(LIBRARIES_PROPERTY, "");
320    return trim(StringUtils.split(sources, SEPARATOR));
321  }
322
323  /**
324   * @param path path to file with third-party library. In case of Java this is path to jar file.
325   *             It can be absolute or relative to project directory.
326   */
327  public void addLibrary(String path) {
328    appendProperty(LIBRARIES_PROPERTY, path);
329  }
330
331  /**
332   * Adds an extension, which would be available in PicoContainer during analysis of this project.
333   *
334   * @since 2.8
335   */
336  public ProjectDefinition addContainerExtension(Object extension) {
337    containerExtensions.add(extension);
338    return this;
339  }
340
341  /**
342   * @since 2.8
343   */
344  public List<Object> getContainerExtensions() {
345    return containerExtensions;
346  }
347
348  /**
349   * @since 2.8
350   */
351  public ProjectDefinition addSubProject(ProjectDefinition child) {
352    subProjects.add(child);
353    child.setParent(this);
354    return this;
355  }
356
357  public ProjectDefinition getParent() {
358    return parent;
359  }
360
361  public void remove() {
362    if (parent != null) {
363      parent.subProjects.remove(this);
364      parent = null;
365      subProjects.clear();
366    }
367  }
368
369  private void setParent(ProjectDefinition parent) {
370    this.parent = parent;
371  }
372
373  /**
374   * @since 2.8
375   */
376  public List<ProjectDefinition> getSubProjects() {
377    return subProjects;
378  }
379
380  private static List<String> trim(String[] strings) {
381    List<String> result = Lists.newArrayList();
382    for (String s : strings) {
383      result.add(StringUtils.trim(s));
384    }
385    return result;
386  }
387
388  @Override
389  public boolean equals(Object o) {
390    if (this == o) {
391      return true;
392    }
393    if (o == null || getClass() != o.getClass()) {
394      return false;
395    }
396    ProjectDefinition that = (ProjectDefinition) o;
397    String key = getKey();
398    if (key != null ? !key.equals(that.getKey()) : that.getKey() != null) {
399      return false;
400    }
401
402    return true;
403  }
404
405  @Override
406  public int hashCode() {
407    String key = getKey();
408    return key != null ? key.hashCode() : 0;
409  }
410}