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.resources;
021
022import org.apache.commons.lang.StringUtils;
023import org.apache.commons.lang.builder.ToStringBuilder;
024import org.sonar.api.batch.SensorContext;
025import org.sonar.api.scan.filesystem.PathResolver;
026import org.sonar.api.utils.WildcardPattern;
027
028import javax.annotation.CheckForNull;
029
030import java.util.List;
031
032/**
033 * This class is an implementation of a resource of type FILE
034 *
035 * @since 1.10
036 */
037public class File extends Resource {
038
039  public static final String SCOPE = Scopes.FILE;
040
041  private String directoryDeprecatedKey;
042  private String filename;
043  private Language language;
044  private Directory parent;
045  private String qualifier = Qualifiers.FILE;
046
047  private File() {
048    // Used by factory method
049  }
050
051  /**
052   * File in project. Key is the path relative to project source directories. It is not the absolute path and it does not include the path
053   * to source directories. Example : <code>new File("org/sonar/foo.sql")</code>. The absolute path may be
054   * c:/myproject/src/main/sql/org/sonar/foo.sql. Project root is c:/myproject and source dir is src/main/sql.
055   * @deprecated since 4.2 use {@link #fromIOFile(java.io.File, Project)}
056   */
057  @Deprecated
058  public File(String relativePathFromSourceDir) {
059    if (relativePathFromSourceDir == null) {
060      throw new IllegalArgumentException("File key is null");
061    }
062    String realKey = parseKey(relativePathFromSourceDir);
063    if (realKey.indexOf(Directory.SEPARATOR) >= 0) {
064      this.directoryDeprecatedKey = Directory.parseKey(StringUtils.substringBeforeLast(relativePathFromSourceDir, Directory.SEPARATOR));
065      this.filename = StringUtils.substringAfterLast(realKey, Directory.SEPARATOR);
066      realKey = new StringBuilder().append(this.directoryDeprecatedKey).append(Directory.SEPARATOR).append(filename).toString();
067
068    } else {
069      this.filename = relativePathFromSourceDir;
070    }
071    setDeprecatedKey(realKey);
072  }
073
074  /**
075   * Creates a file from its containing directory and name
076   * @deprecated since 4.2 use {@link #fromIOFile(java.io.File, Project)}
077   */
078  @Deprecated
079  public File(String relativeDirectoryPathFromSourceDir, String filename) {
080    this.filename = StringUtils.trim(filename);
081    if (StringUtils.isBlank(relativeDirectoryPathFromSourceDir)) {
082      setDeprecatedKey(filename);
083
084    } else {
085      this.directoryDeprecatedKey = Directory.parseKey(relativeDirectoryPathFromSourceDir);
086      setDeprecatedKey(new StringBuilder().append(directoryDeprecatedKey).append(Directory.SEPARATOR).append(this.filename).toString());
087    }
088  }
089
090  /**
091   * Creates a File from its language and its key
092   * @deprecated since 4.2 use {@link #fromIOFile(java.io.File, Project)}
093   */
094  @Deprecated
095  public File(Language language, String relativePathFromSourceDir) {
096    this(relativePathFromSourceDir);
097    this.language = language;
098  }
099
100  /**
101   * Creates a File from language, directory and filename
102   * @deprecated since 4.2 use {@link #fromIOFile(java.io.File, Project)}
103   */
104  @Deprecated
105  public File(Language language, String relativeDirectoryPathFromSourceDir, String filename) {
106    this(relativeDirectoryPathFromSourceDir, filename);
107    this.language = language;
108  }
109
110  /**
111   * {@inheritDoc}
112   *
113   * @see Resource#getParent()
114   */
115  @Override
116  public Directory getParent() {
117    if (parent == null) {
118      parent = new Directory(directoryDeprecatedKey);
119    }
120    return parent;
121  }
122
123  private static String parseKey(String key) {
124    if (StringUtils.isBlank(key)) {
125      return null;
126    }
127    String normalizedKey = key;
128    normalizedKey = normalizedKey.replace('\\', '/');
129    normalizedKey = StringUtils.trim(normalizedKey);
130    return normalizedKey;
131  }
132
133  /**
134   * {@inheritDoc}
135   *
136   * @see Resource#matchFilePattern(String)
137   */
138  @Override
139  public boolean matchFilePattern(String antPattern) {
140    WildcardPattern matcher = WildcardPattern.create(antPattern, Directory.SEPARATOR);
141    return matcher.match(getKey());
142  }
143
144  /**
145   * Creates a File from an io.file and a list of sources directories
146   * @deprecated since 4.2 use {@link #fromIOFile(java.io.File, Project)}
147   */
148  @Deprecated
149  @CheckForNull
150  public static File fromIOFile(java.io.File file, List<java.io.File> sourceDirs) {
151    PathResolver.RelativePath relativePath = new PathResolver().relativePath(sourceDirs, file);
152    if (relativePath != null) {
153      return new File(relativePath.path());
154    }
155    return null;
156  }
157
158  /**
159   * Creates a {@link File} from an absolute {@link java.io.File} and a module.
160   * The returned {@link File} can be then passed for example to
161   * {@link SensorContext#saveMeasure(Resource, org.sonar.api.measures.Measure)}.
162   * @param file absolute path to a file
163   * @param module
164   * @return null if the file is not under module basedir.
165   */
166  @CheckForNull
167  public static File fromIOFile(java.io.File file, Project module) {
168    String relativePathFromBasedir = new PathResolver().relativePath(module.getFileSystem().getBasedir(), file);
169    if (relativePathFromBasedir != null) {
170      return File.create(relativePathFromBasedir);
171    }
172    return null;
173  }
174
175  /**
176   * {@inheritDoc}
177   *
178   * @see Resource#getName()
179   */
180  @Override
181  public String getName() {
182    return filename;
183  }
184
185  /**
186   * {@inheritDoc}
187   *
188   * @see Resource#getLongName()
189   */
190  @Override
191  public String getLongName() {
192    return StringUtils.defaultIfBlank(getPath(), getKey());
193  }
194
195  /**
196   * {@inheritDoc}
197   *
198   * @see Resource#getDescription()
199   */
200  @Override
201  public String getDescription() {
202    return null;
203  }
204
205  /**
206   * {@inheritDoc}
207   *
208   * @see Resource#getLanguage()
209   */
210  @Override
211  public Language getLanguage() {
212    return language;
213  }
214
215  /**
216   * Sets the language of the file
217   */
218  public void setLanguage(Language language) {
219    this.language = language;
220  }
221
222  /**
223   * @return SCOPE_ENTITY
224   */
225  @Override
226  public final String getScope() {
227    return SCOPE;
228  }
229
230  /**
231   * Returns the qualifier associated to this File. Should be QUALIFIER_FILE or QUALIFIER_UNIT_TEST_CLASS
232   */
233  @Override
234  public String getQualifier() {
235    return qualifier;
236  }
237
238  public void setQualifier(String qualifier) {
239    this.qualifier = qualifier;
240  }
241
242  /**
243   * Create a File that is partially initialized. But that's enough to call for example
244   * {@link SensorContext#saveMeasure(Resource, org.sonar.api.measures.Measure)} when resources are already indexed.
245   * Internal use only.
246   * @since 4.2
247   */
248  public static File create(String relativePathFromBasedir) {
249    File file = new File();
250    String normalizedPath = normalize(relativePathFromBasedir);
251    file.setKey(normalizedPath);
252    file.setPath(normalizedPath);
253    String directoryPath;
254    if (normalizedPath != null && normalizedPath.contains(Directory.SEPARATOR)) {
255      directoryPath = StringUtils.substringBeforeLast(normalizedPath, Directory.SEPARATOR);
256    } else {
257      directoryPath = Directory.SEPARATOR;
258    }
259    file.parent = Directory.create(directoryPath);
260    return file;
261  }
262
263  /**
264   * Create a file that is fully initialized. Use for indexing resources.
265   * Internal use only.
266   * @since 4.2
267   */
268  public static File create(String relativePathFromBasedir, String relativePathFromSourceDir, Language language, boolean unitTest) {
269    File file = create(relativePathFromBasedir);
270    file.setLanguage(language);
271    if (relativePathFromSourceDir.contains(Directory.SEPARATOR)) {
272      file.filename = StringUtils.substringAfterLast(relativePathFromSourceDir, Directory.SEPARATOR);
273      file.directoryDeprecatedKey = Directory.parseKey(StringUtils.substringBeforeLast(relativePathFromSourceDir, Directory.SEPARATOR));
274      file.setDeprecatedKey(file.directoryDeprecatedKey + Directory.SEPARATOR + file.filename);
275    } else {
276      file.filename = relativePathFromSourceDir;
277      file.directoryDeprecatedKey = Directory.ROOT;
278      file.setDeprecatedKey(file.filename);
279    }
280    if (unitTest) {
281      file.setQualifier(Qualifiers.UNIT_TEST_FILE);
282    }
283    file.parent.setDeprecatedKey(file.directoryDeprecatedKey);
284    return file;
285  }
286
287  @Override
288  public String toString() {
289    return new ToStringBuilder(this)
290      .append("key", getKey())
291      .append("deprecatedKey", getDeprecatedKey())
292      .append("path", getPath())
293      .append("dir", directoryDeprecatedKey)
294      .append("filename", filename)
295      .append("language", language)
296      .toString();
297  }
298}