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