001/*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2012 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * Sonar 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 * Sonar 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
017 * License along with Sonar; if not, write to the Free Software
018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
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
046  private static final char SEPARATOR = ',';
047
048  private File baseDir;
049  private File workDir;
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 Properties getProperties() {
093    return properties;
094  }
095
096  /**
097   * Copies specified properties into this object.
098   *
099   * @since 2.12
100   */
101  public ProjectDefinition setProperties(Properties properties) {
102    this.properties.putAll(properties);
103    return this;
104  }
105
106  public ProjectDefinition setProperty(String key, String value) {
107    properties.setProperty(key, value);
108    return this;
109  }
110
111  public ProjectDefinition setKey(String key) {
112    properties.setProperty(CoreProperties.PROJECT_KEY_PROPERTY, key);
113    return this;
114  }
115
116  public ProjectDefinition setVersion(String s) {
117    properties.setProperty(CoreProperties.PROJECT_VERSION_PROPERTY, StringUtils.defaultString(s));
118    return this;
119  }
120
121  public ProjectDefinition setName(String s) {
122    properties.setProperty(CoreProperties.PROJECT_NAME_PROPERTY, StringUtils.defaultString(s));
123    return this;
124  }
125
126  public ProjectDefinition setDescription(String s) {
127    properties.setProperty(CoreProperties.PROJECT_DESCRIPTION_PROPERTY, StringUtils.defaultString(s));
128    return this;
129  }
130
131  public String getKey() {
132    return properties.getProperty(CoreProperties.PROJECT_KEY_PROPERTY);
133  }
134
135  public String getVersion() {
136    return properties.getProperty(CoreProperties.PROJECT_VERSION_PROPERTY);
137  }
138
139  public String getName() {
140    String name = properties.getProperty(CoreProperties.PROJECT_NAME_PROPERTY);
141    if (StringUtils.isBlank(name)) {
142      name = "Unnamed - " + getKey();
143    }
144    return name;
145  }
146
147  public String getDescription() {
148    return properties.getProperty(CoreProperties.PROJECT_DESCRIPTION_PROPERTY);
149  }
150
151  private void appendProperty(String key, String value) {
152    String newValue = properties.getProperty(key, "") + SEPARATOR + value;
153    properties.put(key, newValue);
154  }
155
156  public List<String> getSourceDirs() {
157    String sources = properties.getProperty(SOURCE_DIRS_PROPERTY, "");
158    return trim(StringUtils.split(sources, SEPARATOR));
159  }
160
161  /**
162   * @param paths paths to directory with main sources.
163   *              They can be absolute or relative to project base directory.
164   */
165  public ProjectDefinition addSourceDirs(String... paths) {
166    for (String path : paths) {
167      appendProperty(SOURCE_DIRS_PROPERTY, path);
168    }
169    return this;
170  }
171
172  public ProjectDefinition addSourceDirs(File... dirs) {
173    for (File dir : dirs) {
174      addSourceDirs(dir.getAbsolutePath());
175    }
176    return this;
177  }
178
179  public ProjectDefinition resetSourceDirs() {
180    properties.remove(SOURCE_DIRS_PROPERTY);
181    return this;
182  }
183
184  public ProjectDefinition setSourceDirs(String... paths) {
185    resetSourceDirs();
186    return addSourceDirs(paths);
187  }
188
189  public ProjectDefinition setSourceDirs(File... dirs) {
190    resetSourceDirs();
191    for (File dir : dirs) {
192      addSourceDirs(dir.getAbsolutePath());
193    }
194    return this;
195  }
196
197  /**
198   * Adding source files is possible only if no source directories have been set.
199   * Absolute path or relative path from project base dir.
200   */
201  public ProjectDefinition addSourceFiles(String... paths) {
202    for (String path : paths) {
203      appendProperty(SOURCE_FILES_PROPERTY, path);
204    }
205    return this;
206  }
207
208  /**
209   * Adding source files is possible only if no source directories have been set.
210   */
211  public ProjectDefinition addSourceFiles(File... files) {
212    for (File file : files) {
213      addSourceFiles(file.getAbsolutePath());
214    }
215    return this;
216  }
217
218  public List<String> getSourceFiles() {
219    String sources = properties.getProperty(SOURCE_FILES_PROPERTY, "");
220    return trim(StringUtils.split(sources, SEPARATOR));
221  }
222
223  public List<String> getTestDirs() {
224    String sources = properties.getProperty(TEST_DIRS_PROPERTY, "");
225    return trim(StringUtils.split(sources, SEPARATOR));
226  }
227
228  /**
229   * @param paths path to directory with test sources.
230   *              It can be absolute or relative to project directory.
231   */
232  public ProjectDefinition addTestDirs(String... paths) {
233    for (String path : paths) {
234      appendProperty(TEST_DIRS_PROPERTY, path);
235    }
236    return this;
237  }
238
239  public ProjectDefinition addTestDirs(File... dirs) {
240    for (File dir : dirs) {
241      addTestDirs(dir.getAbsolutePath());
242    }
243    return this;
244  }
245
246  public ProjectDefinition setTestDirs(String... paths) {
247    resetTestDirs();
248    return addTestDirs(paths);
249  }
250
251  public ProjectDefinition setTestDirs(File... dirs) {
252    resetTestDirs();
253    for (File dir : dirs) {
254      addTestDirs(dir.getAbsolutePath());
255    }
256    return this;
257  }
258
259  public ProjectDefinition resetTestDirs() {
260    properties.remove(TEST_DIRS_PROPERTY);
261    return this;
262  }
263
264  /**
265   * Adding source files is possible only if no source directories have been set.
266   * Absolute path or relative path from project base dir.
267   */
268  public ProjectDefinition addTestFiles(String... paths) {
269    for (String path : paths) {
270      appendProperty(TEST_FILES_PROPERTY, path);
271    }
272    return this;
273  }
274
275  /**
276   * Adding source files is possible only if no source directories have been set.
277   */
278  public ProjectDefinition addTestFiles(File... files) {
279    for (File file : files) {
280      addTestFiles(file.getAbsolutePath());
281    }
282    return this;
283  }
284
285  public List<String> getTestFiles() {
286    String sources = properties.getProperty(TEST_FILES_PROPERTY, "");
287    return trim(StringUtils.split(sources, SEPARATOR));
288  }
289
290  public List<String> getBinaries() {
291    String sources = properties.getProperty(BINARIES_PROPERTY, "");
292    return trim(StringUtils.split(sources, SEPARATOR));
293  }
294
295  /**
296   * @param path path to directory with compiled source. In case of Java this is directory with class files.
297   *             It can be absolute or relative to project directory.
298   * @TODO currently Sonar supports only one such directory due to dependency on MavenProject
299   */
300  public ProjectDefinition addBinaryDir(String path) {
301    appendProperty(BINARIES_PROPERTY, path);
302    return this;
303  }
304
305  public ProjectDefinition addBinaryDir(File f) {
306    return addBinaryDir(f.getAbsolutePath());
307  }
308
309  public List<String> getLibraries() {
310    String sources = properties.getProperty(LIBRARIES_PROPERTY, "");
311    return trim(StringUtils.split(sources, SEPARATOR));
312  }
313
314  /**
315   * @param path path to file with third-party library. In case of Java this is path to jar file.
316   *             It can be absolute or relative to project directory.
317   */
318  public void addLibrary(String path) {
319    appendProperty(LIBRARIES_PROPERTY, path);
320  }
321
322  /**
323   * Adds an extension, which would be available in PicoContainer during analysis of this project.
324   *
325   * @since 2.8
326   */
327  public ProjectDefinition addContainerExtension(Object extension) {
328    containerExtensions.add(extension);
329    return this;
330  }
331
332  /**
333   * @since 2.8
334   */
335  public List<Object> getContainerExtensions() {
336    return containerExtensions;
337  }
338
339  /**
340   * @since 2.8
341   */
342  public ProjectDefinition addSubProject(ProjectDefinition child) {
343    subProjects.add(child);
344    child.setParent(this);
345    return this;
346  }
347
348  public ProjectDefinition getParent() {
349    return parent;
350  }
351
352  public void remove() {
353    if (parent != null) {
354      parent.subProjects.remove(this);
355      parent = null;
356      subProjects.clear();
357    }
358  }
359
360  private void setParent(ProjectDefinition parent) {
361    this.parent = parent;
362  }
363
364  /**
365   * @since 2.8
366   */
367  public List<ProjectDefinition> getSubProjects() {
368    return subProjects;
369  }
370
371  private static List<String> trim(String[] strings) {
372    List<String> result = Lists.newArrayList();
373    for (String s : strings) {
374      result.add(StringUtils.trim(s));
375    }
376    return result;
377  }
378
379  @Override
380  public boolean equals(Object o) {
381    if (this == o) {
382      return true;
383    }
384    if (o == null || getClass() != o.getClass()) {
385      return false;
386    }
387    ProjectDefinition that = (ProjectDefinition) o;
388    String key = getKey();
389    if (key != null ? !key.equals(that.getKey()) : that.getKey() != null) {
390      return false;
391    }
392
393    return true;
394  }
395
396  @Override
397  public int hashCode() {
398    String key = getKey();
399    return key != null ? key.hashCode() : 0;
400  }
401}