1 /*******************************************************************************
2 * Copyright (c) 2000, 2004 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Common Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/cpl-v10.html
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package net.sourceforge.phpdt.core;
13 import java.util.StringTokenizer;
15 import net.sourceforge.phpdt.core.compiler.CharOperation;
16 import net.sourceforge.phpdt.core.compiler.ITerminalSymbols;
17 import net.sourceforge.phpdt.core.compiler.InvalidInputException;
18 import net.sourceforge.phpdt.internal.compiler.parser.Scanner;
19 import net.sourceforge.phpdt.internal.core.ClasspathEntry;
20 import net.sourceforge.phpdt.internal.core.JavaModelStatus;
21 import net.sourceforge.phpdt.internal.core.util.Util;
23 import org.eclipse.core.resources.IResource;
24 import org.eclipse.core.resources.IWorkspace;
25 import org.eclipse.core.resources.ResourcesPlugin;
26 import org.eclipse.core.runtime.IPath;
27 import org.eclipse.core.runtime.IStatus;
28 import org.eclipse.core.runtime.Status;
31 * Provides methods for checking Java-specific conventions such as name syntax.
33 * This class provides static methods and constants only; it is not intended to be
34 * instantiated or subclassed by clients.
37 public final class JavaConventions {
39 private final static char DOT= '.';
40 private final static Scanner SCANNER = new Scanner();
42 private JavaConventions() {
47 * Returns whether the given package fragment root paths are considered
50 * Two root paths overlap if one is a prefix of the other, or they point to
51 * the same location. However, a JAR is allowed to be nested in a root.
53 * @param rootPath1 the first root path
54 * @param rootPath2 the second root path
55 * @return true if the given package fragment root paths are considered to overlap, false otherwise
56 * @deprecated Overlapping roots are allowed in 2.1
58 public static boolean isOverlappingRoots(IPath rootPath1, IPath rootPath2) {
59 if (rootPath1 == null || rootPath2 == null) {
62 // String extension1 = rootPath1.getFileExtension();
63 // String extension2 = rootPath2.getFileExtension();
64 // if (extension1 != null && (extension1.equalsIgnoreCase(SuffixConstants.EXTENSION_JAR) || extension1.equalsIgnoreCase(SuffixConstants.EXTENSION_ZIP))) {
67 // if (extension2 != null && (extension2.equalsIgnoreCase(SuffixConstants.EXTENSION_JAR) || extension2.equalsIgnoreCase(SuffixConstants.EXTENSION_ZIP))) {
70 return rootPath1.isPrefixOf(rootPath2) || rootPath2.isPrefixOf(rootPath1);
74 * Returns the current identifier extracted by the scanner (without unicode
75 * escapes) from the given id.
76 * Returns <code>null</code> if the id was not valid
78 private static synchronized char[] scannedIdentifier(String id) {
82 String trimmed = id.trim();
83 if (!trimmed.equals(id)) {
87 SCANNER.setSource(id.toCharArray());
88 int token = SCANNER.getNextToken();
89 char[] currentIdentifier;
91 currentIdentifier = SCANNER.getCurrentIdentifierSource();
92 } catch (ArrayIndexOutOfBoundsException e) {
95 int nextToken= SCANNER.getNextToken();
96 if (token == ITerminalSymbols.TokenNameIdentifier
97 && nextToken == ITerminalSymbols.TokenNameEOF
98 && SCANNER.startPosition == SCANNER.source.length) { // to handle case where we had an ArrayIndexOutOfBoundsException
99 // while reading the last token
100 return currentIdentifier;
105 catch (InvalidInputException e) {
111 * Validate the given compilation unit name.
112 * A compilation unit name must obey the following rules:
114 * <li> it must not be null
115 * <li> it must include the <code>".java"</code> suffix
116 * <li> its prefix must be a valid identifier
117 * <li> it must not contain any characters or substrings that are not valid
118 * on the file system on which workspace root is located.
121 * @param name the name of a compilation unit
122 * @return a status object with code <code>IStatus.OK</code> if
123 * the given name is valid as a compilation unit name, otherwise a status
124 * object indicating what is wrong with the name
126 public static IStatus validateCompilationUnitName(String name) {
128 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.unit.nullName"), null); //$NON-NLS-1$
130 if (!net.sourceforge.phpdt.internal.compiler.util.Util.isJavaFileName(name)) {
131 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.unit.notJavaName"), null); //$NON-NLS-1$
135 index = name.lastIndexOf('.');
137 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.unit.notJavaName"), null); //$NON-NLS-1$
139 identifier = name.substring(0, index);
140 // JSR-175 metadata strongly recommends "package-info.java" as the
141 // file in which to store package annotations and
142 // the package-level spec (replaces package.html)
143 if (!identifier.equals("package-info")) { //$NON-NLS-1$
144 IStatus status = validateIdentifier(identifier);
145 if (!status.isOK()) {
149 IStatus status = ResourcesPlugin.getWorkspace().validateName(name, IResource.FILE);
150 if (!status.isOK()) {
153 return JavaModelStatus.VERIFIED_OK;
157 * Validate the given .class file name.
158 * A .class file name must obey the following rules:
160 * <li> it must not be null
161 * <li> it must include the <code>".class"</code> suffix
162 * <li> its prefix must be a valid identifier
163 * <li> it must not contain any characters or substrings that are not valid
164 * on the file system on which workspace root is located.
167 * @param name the name of a .class file
168 * @return a status object with code <code>IStatus.OK</code> if
169 * the given name is valid as a .class file name, otherwise a status
170 * object indicating what is wrong with the name
173 // public static IStatus validateClassFileName(String name) {
174 // if (name == null) {
175 // return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.classFile.nullName"), null); //$NON-NLS-1$
177 // if (!net.sourceforge.phpdt.internal.compiler.util.Util.isClassFileName(name)) {
178 // return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.classFile.notClassFileName"), null); //$NON-NLS-1$
180 // String identifier;
182 // index = name.lastIndexOf('.');
183 // if (index == -1) {
184 // return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.classFile.notClassFileName"), null); //$NON-NLS-1$
186 // identifier = name.substring(0, index);
187 // IStatus status = validateIdentifier(identifier);
188 // if (!status.isOK()) {
191 // status = ResourcesPlugin.getWorkspace().validateName(name, IResource.FILE);
192 // if (!status.isOK()) {
195 // return JavaModelStatus.VERIFIED_OK;
199 * Validate the given field name.
201 * Syntax of a field name corresponds to VariableDeclaratorId (JLS2 8.3).
202 * For example, <code>"x"</code>.
204 * @param name the name of a field
205 * @return a status object with code <code>IStatus.OK</code> if
206 * the given name is valid as a field name, otherwise a status
207 * object indicating what is wrong with the name
209 public static IStatus validateFieldName(String name) {
210 return validateIdentifier(name);
214 * Validate the given Java identifier.
215 * The identifier must not have the same spelling as a Java keyword,
216 * boolean literal (<code>"true"</code>, <code>"false"</code>), or null literal (<code>"null"</code>).
217 * See section 3.8 of the <em>Java Language Specification, Second Edition</em> (JLS2).
218 * A valid identifier can act as a simple type name, method name or field name.
220 * @param id the Java identifier
221 * @return a status object with code <code>IStatus.OK</code> if
222 * the given identifier is a valid Java identifier, otherwise a status
223 * object indicating what is wrong with the identifier
225 public static IStatus validateIdentifier(String id) {
226 if (scannedIdentifier(id) != null) {
227 return JavaModelStatus.VERIFIED_OK;
229 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.illegalIdentifier", id), null); //$NON-NLS-1$
234 * Validate the given import declaration name.
236 * The name of an import corresponds to a fully qualified type name
237 * or an on-demand package name as defined by ImportDeclaration (JLS2 7.5).
238 * For example, <code>"java.util.*"</code> or <code>"java.util.Hashtable"</code>.
240 * @param name the import declaration
241 * @return a status object with code <code>IStatus.OK</code> if
242 * the given name is valid as an import declaration, otherwise a status
243 * object indicating what is wrong with the name
245 public static IStatus validateImportDeclaration(String name) {
246 if (name == null || name.length() == 0) {
247 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.import.nullImport"), null); //$NON-NLS-1$
249 if (name.charAt(name.length() - 1) == '*') {
250 if (name.charAt(name.length() - 2) == '.') {
251 return validatePackageName(name.substring(0, name.length() - 2));
253 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.import.unqualifiedImport"), null); //$NON-NLS-1$
256 return validatePackageName(name);
260 * Validate the given Java type name, either simple or qualified.
261 * For example, <code>"java.lang.Object"</code>, or <code>"Object"</code>.
264 * @param name the name of a type
265 * @return a status object with code <code>IStatus.OK</code> if
266 * the given name is valid as a Java type name,
267 * a status with code <code>IStatus.WARNING</code>
268 * indicating why the given name is discouraged,
269 * otherwise a status object indicating what is wrong with
272 public static IStatus validateJavaTypeName(String name) {
274 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.type.nullName"), null); //$NON-NLS-1$
276 String trimmed = name.trim();
277 if (!name.equals(trimmed)) {
278 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.type.nameWithBlanks"), null); //$NON-NLS-1$
280 int index = name.lastIndexOf('.');
284 scannedID = scannedIdentifier(name);
287 String pkg = name.substring(0, index).trim();
288 IStatus status = validatePackageName(pkg);
289 if (!status.isOK()) {
292 String type = name.substring(index + 1).trim();
293 scannedID = scannedIdentifier(type);
296 if (scannedID != null) {
297 IStatus status = ResourcesPlugin.getWorkspace().validateName(new String(scannedID), IResource.FILE);
298 if (!status.isOK()) {
301 if (CharOperation.contains('$', scannedID)) {
302 return new Status(IStatus.WARNING, JavaCore.PLUGIN_ID, -1, Util.bind("convention.type.dollarName"), null); //$NON-NLS-1$
304 if ((scannedID.length > 0 && Character.isLowerCase(scannedID[0]))) {
305 return new Status(IStatus.WARNING, JavaCore.PLUGIN_ID, -1, Util.bind("convention.type.lowercaseName"), null); //$NON-NLS-1$
307 return JavaModelStatus.VERIFIED_OK;
309 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.type.invalidName", name), null); //$NON-NLS-1$
314 * Validate the given method name.
315 * The special names "<init>" and "<clinit>" are not valid.
317 * The syntax for a method name is defined by Identifier
318 * of MethodDeclarator (JLS2 8.4). For example "println".
320 * @param name the name of a method
321 * @return a status object with code <code>IStatus.OK</code> if
322 * the given name is valid as a method name, otherwise a status
323 * object indicating what is wrong with the name
325 public static IStatus validateMethodName(String name) {
327 return validateIdentifier(name);
331 * Validate the given package name.
333 * The syntax of a package name corresponds to PackageName as
334 * defined by PackageDeclaration (JLS2 7.4). For example, <code>"java.lang"</code>.
336 * Note that the given name must be a non-empty package name (that is, attempting to
337 * validate the default package will return an error status.)
338 * Also it must not contain any characters or substrings that are not valid
339 * on the file system on which workspace root is located.
341 * @param name the name of a package
342 * @return a status object with code <code>IStatus.OK</code> if
343 * the given name is valid as a package name, otherwise a status
344 * object indicating what is wrong with the name
346 public static IStatus validatePackageName(String name) {
349 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.package.nullName"), null); //$NON-NLS-1$
352 if ((length = name.length()) == 0) {
353 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.package.emptyName"), null); //$NON-NLS-1$
355 if (name.charAt(0) == DOT || name.charAt(length-1) == DOT) {
356 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.package.dotName"), null); //$NON-NLS-1$
358 if (CharOperation.isWhitespace(name.charAt(0)) || CharOperation.isWhitespace(name.charAt(name.length() - 1))) {
359 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.package.nameWithBlanks"), null); //$NON-NLS-1$
362 while (dot != -1 && dot < length-1) {
363 if ((dot = name.indexOf(DOT, dot+1)) != -1 && dot < length-1 && name.charAt(dot+1) == DOT) {
364 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.package.consecutiveDotsName"), null); //$NON-NLS-1$
367 IWorkspace workspace = ResourcesPlugin.getWorkspace();
368 StringTokenizer st = new StringTokenizer(name, new String(new char[] {DOT}));
369 boolean firstToken = true;
370 IStatus warningStatus = null;
371 while (st.hasMoreTokens()) {
372 String typeName = st.nextToken();
373 typeName = typeName.trim(); // grammar allows spaces
374 char[] scannedID = scannedIdentifier(typeName);
375 if (scannedID == null) {
376 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.illegalIdentifier", typeName), null); //$NON-NLS-1$
378 IStatus status = workspace.validateName(new String(scannedID), IResource.FOLDER);
379 if (!status.isOK()) {
382 if (firstToken && scannedID.length > 0 && Character.isUpperCase(scannedID[0])) {
383 if (warningStatus == null) {
384 warningStatus = new Status(IStatus.WARNING, JavaCore.PLUGIN_ID, -1, Util.bind("convention.package.uppercaseName"), null); //$NON-NLS-1$
389 if (warningStatus != null) {
390 return warningStatus;
392 return JavaModelStatus.VERIFIED_OK;
396 * Validate a given classpath and output location for a project, using the following rules:
398 * <li> Classpath entries cannot collide with each other; that is, all entry paths must be unique.
399 * <li> The project output location path cannot be null, must be absolute and located inside the project.
400 * <li> Specific output locations (specified on source entries) can be null, if not they must be located inside the project,
401 * <li> A project entry cannot refer to itself directly (that is, a project cannot prerequisite itself).
402 * <li> Classpath entries or output locations cannot coincidate or be nested in each other, except for the following scenarii listed below:
403 * <ul><li> A source folder can coincidate with its own output location, in which case this output can then contain library archives.
404 * However, a specific output location cannot coincidate with any library or a distinct source folder than the one referring to it. </li>
405 * <li> A source/library folder can be nested in any source folder as long as the nested folder is excluded from the enclosing one. </li>
406 * <li> An output location can be nested in a source folder, if the source folder coincidates with the project itself, or if the output
407 * location is excluded from the source folder.
411 * Note that the classpath entries are not validated automatically. Only bound variables or containers are considered
412 * in the checking process (this allows to perform a consistency check on a classpath which has references to
413 * yet non existing projects, folders, ...).
415 * This validation is intended to anticipate classpath issues prior to assigning it to a project. In particular, it will automatically
416 * be performed during the classpath setting operation (if validation fails, the classpath setting will not complete).
418 * @param javaProject the given java project
419 * @param rawClasspath the given classpath
420 * @param projectOutputLocation the given output location
421 * @return a status object with code <code>IStatus.OK</code> if
422 * the given classpath and output location are compatible, otherwise a status
423 * object indicating what is wrong with the classpath or output location
426 public static IJavaModelStatus validateClasspath(IJavaProject javaProject, IClasspathEntry[] rawClasspath, IPath projectOutputLocation) {
428 return ClasspathEntry.validateClasspath(javaProject, rawClasspath, projectOutputLocation);
432 * Returns a Java model status describing the problem related to this classpath entry if any,
433 * a status object with code <code>IStatus.OK</code> if the entry is fine (that is, if the
434 * given classpath entry denotes a valid element to be referenced onto a classpath).
436 * @param project the given java project
437 * @param entry the given classpath entry
438 * @param checkSourceAttachment a flag to determine if source attachement should be checked
439 * @return a java model status describing the problem related to this classpath entry if any, a status object with code <code>IStatus.OK</code> if the entry is fine
442 public static IJavaModelStatus validateClasspathEntry(IJavaProject project, IClasspathEntry entry, boolean checkSourceAttachment){
443 return ClasspathEntry.validateClasspathEntry(project, entry, checkSourceAttachment, true/*recurse in container*/);