001/*
002 * SonarQube
003 * Copyright (C) 2009-2016 SonarSource SA
004 * mailto:contact AT sonarsource DOT com
005 *
006 * This program 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 * This program 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  public static final String TESTS_PROPERTY = "sonar.tests";
046
047  public static final String BUILD_DIR_PROPERTY = "sonar.buildDir";
048
049  private static final char SEPARATOR = ',';
050
051  private File baseDir;
052  private File workDir;
053  private File buildDir;
054  private Map<String, String> properties = new HashMap<>();
055  private ProjectDefinition parent = null;
056  private List<ProjectDefinition> subProjects = new ArrayList<>();
057
058  private ProjectDefinition(Properties p) {
059    for (Entry<Object, Object> entry : p.entrySet()) {
060      this.properties.put(entry.getKey().toString(), entry.getValue().toString());
061    }
062  }
063
064  public static ProjectDefinition create() {
065    return new ProjectDefinition(new Properties());
066  }
067
068  public ProjectDefinition setBaseDir(File baseDir) {
069    this.baseDir = baseDir;
070    return this;
071  }
072
073  public File getBaseDir() {
074    return baseDir;
075  }
076
077  public ProjectDefinition setWorkDir(File workDir) {
078    this.workDir = workDir;
079    return this;
080  }
081
082  public File getWorkDir() {
083    return workDir;
084  }
085
086  public ProjectDefinition setBuildDir(File d) {
087    this.buildDir = d;
088    return this;
089  }
090
091  public File getBuildDir() {
092    return buildDir;
093  }
094
095  /**
096   * @deprecated since 5.0 use {@link #properties()}
097   */
098  @Deprecated
099  public Properties getProperties() {
100    Properties result = new Properties();
101    for (Map.Entry<String, String> entry : properties.entrySet()) {
102      result.setProperty(entry.getKey(), entry.getValue());
103    }
104    return result;
105  }
106
107  public Map<String, String> properties() {
108    return properties;
109  }
110
111  /**
112   * Copies specified properties into this object.
113   *
114   * @since 2.12
115   * @deprecated since 5.0 use {@link #setProperties(Map)}
116   */
117  @Deprecated
118  public ProjectDefinition setProperties(Properties properties) {
119    for (Entry<Object, Object> entry : properties.entrySet()) {
120      this.properties.put(entry.getKey().toString(), entry.getValue().toString());
121    }
122    return this;
123  }
124
125  public ProjectDefinition setProperties(Map<String, String> properties) {
126    this.properties.putAll(properties);
127    return this;
128  }
129
130  public ProjectDefinition setProperty(String key, String value) {
131    properties.put(key, value);
132    return this;
133  }
134
135  public ProjectDefinition setKey(String key) {
136    properties.put(CoreProperties.PROJECT_KEY_PROPERTY, key);
137    return this;
138  }
139
140  public ProjectDefinition setVersion(String s) {
141    properties.put(CoreProperties.PROJECT_VERSION_PROPERTY, StringUtils.defaultString(s));
142    return this;
143  }
144
145  public ProjectDefinition setName(String s) {
146    properties.put(CoreProperties.PROJECT_NAME_PROPERTY, StringUtils.defaultString(s));
147    return this;
148  }
149
150  public ProjectDefinition setDescription(String s) {
151    properties.put(CoreProperties.PROJECT_DESCRIPTION_PROPERTY, StringUtils.defaultString(s));
152    return this;
153  }
154
155  public String getKey() {
156    return properties.get(CoreProperties.PROJECT_KEY_PROPERTY);
157  }
158
159  /**
160   * @since 4.5
161   */
162  public String getKeyWithBranch() {
163    String branch = getBranch();
164    String projectKey = getKey();
165    if (StringUtils.isNotBlank(branch)) {
166      projectKey = String.format("%s:%s", projectKey, branch);
167    }
168    return projectKey;
169  }
170
171  @CheckForNull
172  public String getBranch() {
173    String branch = properties.get(CoreProperties.PROJECT_BRANCH_PROPERTY);
174    if (StringUtils.isNotBlank(branch)) {
175      return branch;
176    } else if (getParent() != null) {
177      return getParent().getBranch();
178    }
179    return null;
180  }
181
182  public String getVersion() {
183    return properties.get(CoreProperties.PROJECT_VERSION_PROPERTY);
184  }
185
186  public String getName() {
187    String name = properties.get(CoreProperties.PROJECT_NAME_PROPERTY);
188    if (StringUtils.isBlank(name)) {
189      name = "Unnamed - " + getKey();
190    }
191    return name;
192  }
193
194  public String getDescription() {
195    return properties.get(CoreProperties.PROJECT_DESCRIPTION_PROPERTY);
196  }
197
198  private void appendProperty(String key, String value) {
199    String current = (String) ObjectUtils.defaultIfNull(properties.get(key), "");
200    if (StringUtils.isBlank(current)) {
201      properties.put(key, value);
202    } else {
203      properties.put(key, current + SEPARATOR + value);
204    }
205  }
206
207  /**
208   * @return Source files and folders.
209   */
210  public List<String> sources() {
211    String sources = (String) ObjectUtils.defaultIfNull(properties.get(SOURCES_PROPERTY), "");
212    return trim(StringUtils.split(sources, SEPARATOR));
213  }
214
215  /**
216   * @param paths paths to file or directory with main sources.
217   *              They can be absolute or relative to project base directory.
218   */
219  public ProjectDefinition addSources(String... paths) {
220    for (String path : paths) {
221      appendProperty(SOURCES_PROPERTY, path);
222    }
223    return this;
224  }
225
226  public ProjectDefinition addSources(File... fileOrDirs) {
227    for (File fileOrDir : fileOrDirs) {
228      addSources(fileOrDir.getAbsolutePath());
229    }
230    return this;
231  }
232
233  public ProjectDefinition resetSources() {
234    properties.remove(SOURCES_PROPERTY);
235    return this;
236  }
237
238  public ProjectDefinition setSources(String... paths) {
239    resetSources();
240    return addSources(paths);
241  }
242
243  public ProjectDefinition setSources(File... filesOrDirs) {
244    resetSources();
245    for (File fileOrDir : filesOrDirs) {
246      addSources(fileOrDir.getAbsolutePath());
247    }
248    return this;
249  }
250
251  public List<String> tests() {
252    String sources = (String) ObjectUtils.defaultIfNull(properties.get(TESTS_PROPERTY), "");
253    return trim(StringUtils.split(sources, SEPARATOR));
254  }
255
256  /**
257   * @param paths path to files or directories with test sources.
258   *              It can be absolute or relative to project directory.
259   */
260  public ProjectDefinition addTests(String... paths) {
261    for (String path : paths) {
262      appendProperty(TESTS_PROPERTY, path);
263    }
264    return this;
265  }
266
267  public ProjectDefinition addTests(File... fileOrDirs) {
268    for (File fileOrDir : fileOrDirs) {
269      addTests(fileOrDir.getAbsolutePath());
270    }
271    return this;
272  }
273
274  public ProjectDefinition setTests(String... paths) {
275    resetTests();
276    return addTests(paths);
277  }
278
279  public ProjectDefinition setTests(File... fileOrDirs) {
280    resetTests();
281    for (File dir : fileOrDirs) {
282      addTests(dir.getAbsolutePath());
283    }
284    return this;
285  }
286
287  public ProjectDefinition resetTests() {
288    properties.remove(TESTS_PROPERTY);
289    return this;
290  }
291
292  /**
293   * @since 2.8
294   */
295  public ProjectDefinition addSubProject(ProjectDefinition child) {
296    subProjects.add(child);
297    child.setParent(this);
298    return this;
299  }
300
301  public ProjectDefinition getParent() {
302    return parent;
303  }
304
305  public void remove() {
306    if (parent != null) {
307      parent.subProjects.remove(this);
308      parent = null;
309      subProjects.clear();
310    }
311  }
312
313  private void setParent(ProjectDefinition parent) {
314    this.parent = parent;
315  }
316
317  /**
318   * @since 2.8
319   */
320  public List<ProjectDefinition> getSubProjects() {
321    return subProjects;
322  }
323
324  private static List<String> trim(String[] strings) {
325    List<String> result = new ArrayList<>();
326    for (String s : strings) {
327      result.add(StringUtils.trim(s));
328    }
329    return result;
330  }
331
332  @Override
333  public boolean equals(Object o) {
334    if (this == o) {
335      return true;
336    }
337    if (o == null || getClass() != o.getClass()) {
338      return false;
339    }
340    ProjectDefinition that = (ProjectDefinition) o;
341    String key = getKey();
342    return !((key != null) ? !key.equals(that.getKey()) : (that.getKey() != null));
343
344  }
345
346  @Override
347  public int hashCode() {
348    String key = getKey();
349    return key != null ? key.hashCode() : 0;
350  }
351}