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.resources;
021
022import java.util.ArrayList;
023import java.util.Date;
024import java.util.List;
025import javax.annotation.CheckForNull;
026import javax.annotation.Nullable;
027import org.apache.commons.lang.StringUtils;
028import org.apache.commons.lang.builder.ToStringBuilder;
029import org.sonar.api.CoreProperties;
030import org.sonar.api.component.Component;
031import org.sonar.api.config.Settings;
032
033/**
034 * A class that manipulates Projects in the Sonar way.
035 *
036 * @since 1.10
037 */
038public class Project extends Resource implements Component {
039
040  /**
041   * Internal use
042   */
043  public static final Language NONE_LANGUAGE = new AbstractLanguage("none", "None") {
044    @Override
045    public String[] getFileSuffixes() {
046      return new String[0];
047    }
048  };
049
050  static final String MAVEN_KEY_FORMAT = "%s:%s";
051  private static final String BRANCH_KEY_FORMAT = "%s:%s";
052
053  public static final String SCOPE = Scopes.PROJECT;
054
055  /**
056   * Enumerates the type of possible analysis
057   * @deprecated since 4.4 Since 4.3 SQ will no more run tests. So basically it's always reuse report.
058   */
059  @Deprecated
060  public enum AnalysisType {
061    STATIC, DYNAMIC, REUSE_REPORTS;
062
063    /**
064     * @param includeReuseReportMode whether to count report reuse as dynamic or not
065     * @return whether this a dynamic analysis
066     */
067    public boolean isDynamic(boolean includeReuseReportMode) {
068      return equals(Project.AnalysisType.DYNAMIC) ||
069        (equals(Project.AnalysisType.REUSE_REPORTS) && includeReuseReportMode);
070    }
071  }
072
073  private String branch;
074  private String name;
075  private String description;
076  private Language language;
077  private Date analysisDate;
078  private AnalysisType analysisType;
079  private String analysisVersion;
080  private Settings settings;
081
082  // For internal use
083  private java.io.File baseDir;
084
085  // modules tree
086  private Project parent;
087  private List<Project> modules = new ArrayList<>();
088
089  public Project(String key) {
090    setKey(key);
091    setEffectiveKey(key);
092  }
093
094  public Project(String key, String branch, String name) {
095    if (StringUtils.isNotBlank(branch)) {
096      setKey(String.format(BRANCH_KEY_FORMAT, key, branch));
097      this.name = String.format("%s %s", name, branch);
098    } else {
099      setKey(key);
100      this.name = name;
101    }
102    setEffectiveKey(getKey());
103    this.branch = branch;
104  }
105
106  public String getBranch() {
107    return branch;
108  }
109
110  /**
111   * For internal use only.
112   */
113  public Project setBranch(String branch) {
114    this.branch = branch;
115    return this;
116  }
117
118  @Override
119  public String getName() {
120    return name;
121  }
122
123  @Override
124  public String getLongName() {
125    return name;
126  }
127
128  @Override
129  public String getDescription() {
130    return description;
131  }
132
133  /**
134   * For internal use only.
135   */
136  public Project setName(String name) {
137    this.name = name;
138    return this;
139  }
140
141  /**
142   * For internal use only.
143   */
144  public Project setDescription(String description) {
145    this.description = description;
146    return this;
147  }
148
149  /**
150   * @return whether the current project is root project
151   */
152  public boolean isRoot() {
153    return getParent() == null;
154  }
155
156  public Project getRoot() {
157    return parent == null ? this : parent.getRoot();
158  }
159
160  /**
161   * @return whether the current project is a module
162   */
163  public boolean isModule() {
164    return !isRoot();
165  }
166
167  /**
168   * @deprecated since 4.4 Since 4.3 SQ will no more run tests. So basically it's always reuse report.
169   */
170  @Deprecated
171  public AnalysisType getAnalysisType() {
172    return analysisType;
173  }
174
175  /**
176   * @deprecated since 4.4 Since 4.3 SQ will no more run tests. So basically it's always reuse report.
177   */
178  @Deprecated
179  public Project setAnalysisType(AnalysisType at) {
180    this.analysisType = at;
181    return this;
182  }
183
184  /**
185   * whether it's the latest analysis done on this project (displayed in sonar dashboard) or an analysis on a past revision.
186   *
187   * @since 2.0
188   * @deprecated in 3.6. The analysis is now always the latest one (past analysis must be done in a chronological order). See http://jira.sonarsource.com/browse/SONAR-4334
189   */
190  @Deprecated
191  public boolean isLatestAnalysis() {
192    return true;
193  }
194
195  /**
196   * For internal use only.
197   *
198   * @deprecated in 3.6. It's not possible to analyze a project before the latest known quality snapshot.
199   * See http://jira.sonarsource.com/browse/SONAR-4334
200   */
201  @Deprecated
202  public Project setLatestAnalysis(boolean b) {
203    if (!b) {
204      throw new UnsupportedOperationException("The analysis is always the latest one. " +
205        "Past analysis must be done in a chronological order.");
206    }
207    return this;
208  }
209
210  /**
211   * @return the project language when there is only one language
212   * @deprecated since 4.2 use {@link org.sonar.api.batch.fs.FileSystem#languages()}
213   */
214  @Deprecated
215  @Override
216  public Language getLanguage() {
217    return language;
218  }
219
220  /**
221   * Internal use
222   */
223  public Project setLanguage(Language language) {
224    this.language = language;
225    return this;
226  }
227
228  /**
229   * @return the language key or empty if no language is specified
230   * @deprecated since 4.2 use {@link org.sonar.api.batch.fs.FileSystem#languages()}
231   */
232  @Deprecated
233  public String getLanguageKey() {
234    if (settings == null) {
235      throw new IllegalStateException("Project is not yet initialized");
236    }
237    return StringUtils.defaultIfEmpty(settings.getString(CoreProperties.PROJECT_LANGUAGE_PROPERTY), "");
238  }
239
240  /**
241   * Internal use
242   */
243  public Project setSettings(Settings settings) {
244    this.settings = settings;
245    return this;
246  }
247
248  /**
249   * Internal use for backward compatibility. Settings should be retrieved as an IoC dependency.
250   * @deprecated since 5.0
251   */
252  @Deprecated
253  public Settings getSettings() {
254    return settings;
255  }
256
257  /**
258   * For internal use only.
259   */
260  public Project setAnalysisDate(Date analysisDate) {
261    this.analysisDate = analysisDate;
262    return this;
263  }
264
265  /**
266   * For internal use only.
267   */
268  public Project setAnalysisVersion(String analysisVersion) {
269    this.analysisVersion = analysisVersion;
270    return this;
271  }
272
273  /**
274   * @return the scope of the current object
275   */
276  @Override
277  public String getScope() {
278    return Scopes.PROJECT;
279  }
280
281  /**
282   * @return the qualifier of the current object
283   */
284  @Override
285  public String getQualifier() {
286    return isRoot() ? Qualifiers.PROJECT : Qualifiers.MODULE;
287  }
288
289  @Override
290  public boolean matchFilePattern(String antPattern) {
291    return false;
292  }
293
294  @CheckForNull
295  @Override
296  public Project getParent() {
297    return parent;
298  }
299
300  /**
301   * For internal use only.
302   */
303  public Project setParent(Project parent) {
304    this.parent = parent;
305    if (parent != null) {
306      parent.modules.add(this);
307    }
308    return this;
309  }
310
311  /**
312   * For internal use only.
313   */
314  public void removeFromParent() {
315    if (parent != null) {
316      parent.modules.remove(this);
317    }
318  }
319
320  /**
321   * @return the list of modules
322   */
323  public List<Project> getModules() {
324    return modules;
325  }
326
327  /**
328   * @return the current version of the project
329   */
330  public String getAnalysisVersion() {
331    return analysisVersion;
332  }
333
334  /**
335   * @return the analysis date, i.e. the date that will be used to store the snapshot
336   */
337  public Date getAnalysisDate() {
338    return analysisDate;
339  }
340
341  public static Project createFromMavenIds(String groupId, String artifactId) {
342    return createFromMavenIds(groupId, artifactId, null);
343  }
344
345  public static Project createFromMavenIds(String groupId, String artifactId, @Nullable String branch) {
346    return new Project(String.format(MAVEN_KEY_FORMAT, groupId, artifactId), branch, "");
347  }
348
349  @Override
350  public String toString() {
351    return new ToStringBuilder(this)
352      .append("id", getId())
353      .append("key", getKey())
354      .append("qualifier", getQualifier())
355      .toString();
356  }
357
358  @Override
359  public String key() {
360    return getKey();
361  }
362
363  @Override
364  public String name() {
365    return getName();
366  }
367
368  @Override
369  public String path() {
370    return getPath();
371  }
372
373  @Override
374  public String longName() {
375    return getLongName();
376  }
377
378  @Override
379  public String qualifier() {
380    return getQualifier();
381  }
382
383  // For internal use
384  public void setBaseDir(java.io.File baseDir) {
385    this.baseDir = baseDir;
386  }
387
388  java.io.File getBaseDir() {
389    return baseDir;
390  }
391}