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
34 * be instantiated or subclassed by clients.
37 public final class JavaConventions {
39 private final static char DOT = '.';
41 private final static Scanner SCANNER = new Scanner();
43 private JavaConventions() {
48 * Returns whether the given package fragment root paths are considered to
51 * Two root paths overlap if one is a prefix of the other, or they point to
52 * the same location. However, a JAR is allowed to be nested in a root.
57 * the second root path
58 * @return true if the given package fragment root paths are considered to
59 * overlap, false otherwise
60 * @deprecated Overlapping roots are allowed in 2.1
62 public static boolean isOverlappingRoots(IPath rootPath1, IPath rootPath2) {
63 if (rootPath1 == null || rootPath2 == null) {
66 // String extension1 = rootPath1.getFileExtension();
67 // String extension2 = rootPath2.getFileExtension();
68 // if (extension1 != null &&
69 // (extension1.equalsIgnoreCase(SuffixConstants.EXTENSION_JAR) ||
70 // extension1.equalsIgnoreCase(SuffixConstants.EXTENSION_ZIP))) {
73 // if (extension2 != null &&
74 // (extension2.equalsIgnoreCase(SuffixConstants.EXTENSION_JAR) ||
75 // extension2.equalsIgnoreCase(SuffixConstants.EXTENSION_ZIP))) {
78 return rootPath1.isPrefixOf(rootPath2)
79 || rootPath2.isPrefixOf(rootPath1);
83 * Returns the current identifier extracted by the scanner (without unicode
84 * escapes) from the given id. Returns <code>null</code> if the id was not
87 private static synchronized char[] scannedIdentifier(String id) {
91 String trimmed = id.trim();
92 if (!trimmed.equals(id)) {
96 SCANNER.setSource(id.toCharArray());
97 int token = SCANNER.getNextToken();
98 char[] currentIdentifier;
100 currentIdentifier = SCANNER.getCurrentIdentifierSource();
101 } catch (ArrayIndexOutOfBoundsException e) {
104 int nextToken = SCANNER.getNextToken();
105 if (token == ITerminalSymbols.TokenNameIdentifier
106 && nextToken == ITerminalSymbols.TokenNameEOF
107 && SCANNER.startPosition == SCANNER.source.length) { // to
114 // ArrayIndexOutOfBoundsException
115 // while reading the last token
116 return currentIdentifier;
120 } catch (InvalidInputException e) {
126 * Validate the given compilation unit name. A compilation unit name must
127 * obey the following rules:
129 * <li> it must not be null
130 * <li> it must include the <code>".java"</code> suffix
131 * <li> its prefix must be a valid identifier
132 * <li> it must not contain any characters or substrings that are not valid
133 * on the file system on which workspace root is located.
138 * the name of a compilation unit
139 * @return a status object with code <code>IStatus.OK</code> if the given
140 * name is valid as a compilation unit name, otherwise a status
141 * object indicating what is wrong with the name
143 public static IStatus validateCompilationUnitName(String name) {
145 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util
146 .bind("convention.unit.nullName"), null); //$NON-NLS-1$
148 if (!net.sourceforge.phpdt.internal.compiler.util.Util
149 .isJavaFileName(name)) {
150 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util
151 .bind("convention.unit.notJavaName"), null); //$NON-NLS-1$
155 index = name.lastIndexOf('.');
157 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util
158 .bind("convention.unit.notJavaName"), null); //$NON-NLS-1$
160 identifier = name.substring(0, index);
161 // JSR-175 metadata strongly recommends "package-info.java" as the
162 // file in which to store package annotations and
163 // the package-level spec (replaces package.html)
164 if (!identifier.equals("package-info")) { //$NON-NLS-1$
165 IStatus status = validateIdentifier(identifier);
166 if (!status.isOK()) {
170 IStatus status = ResourcesPlugin.getWorkspace().validateName(name,
172 if (!status.isOK()) {
175 return JavaModelStatus.VERIFIED_OK;
179 * Validate the given .class file name. A .class file name must obey the
182 * <li> it must not be null
183 * <li> it must include the <code>".class"</code> suffix
184 * <li> its prefix must be a valid identifier
185 * <li> it must not contain any characters or substrings that are not valid
186 * on the file system on which workspace root is located.
191 * the name of a .class file
192 * @return a status object with code <code>IStatus.OK</code> if the given
193 * name is valid as a .class file name, otherwise a status object
194 * indicating what is wrong with the name
197 // public static IStatus validateClassFileName(String name) {
198 // if (name == null) {
199 // return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1,
200 // Util.bind("convention.classFile.nullName"), null); //$NON-NLS-1$
203 // (!net.sourceforge.phpdt.internal.compiler.util.Util.isClassFileName(name))
205 // return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1,
206 // Util.bind("convention.classFile.notClassFileName"), null); //$NON-NLS-1$
208 // String identifier;
210 // index = name.lastIndexOf('.');
211 // if (index == -1) {
212 // return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1,
213 // Util.bind("convention.classFile.notClassFileName"), null); //$NON-NLS-1$
215 // identifier = name.substring(0, index);
216 // IStatus status = validateIdentifier(identifier);
217 // if (!status.isOK()) {
220 // status = ResourcesPlugin.getWorkspace().validateName(name,
222 // if (!status.isOK()) {
225 // return JavaModelStatus.VERIFIED_OK;
228 * Validate the given field name.
230 * Syntax of a field name corresponds to VariableDeclaratorId (JLS2 8.3).
231 * For example, <code>"x"</code>.
234 * the name of a field
235 * @return a status object with code <code>IStatus.OK</code> if the given
236 * name is valid as a field name, otherwise a status object
237 * indicating what is wrong with the name
239 public static IStatus validateFieldName(String name) {
240 return validateIdentifier(name);
244 * Validate the given Java identifier. The identifier must not have the same
245 * spelling as a Java keyword, boolean literal (<code>"true"</code>,
246 * <code>"false"</code>), or null literal (<code>"null"</code>). See
247 * section 3.8 of the <em>Java Language Specification, Second Edition</em>
248 * (JLS2). A valid identifier can act as a simple type name, method name or
252 * the Java identifier
253 * @return a status object with code <code>IStatus.OK</code> if the given
254 * identifier is a valid Java identifier, otherwise a status object
255 * indicating what is wrong with the identifier
257 public static IStatus validateIdentifier(String id) {
258 if (scannedIdentifier(id) != null) {
259 return JavaModelStatus.VERIFIED_OK;
261 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind(
262 "convention.illegalIdentifier", id), null); //$NON-NLS-1$
267 * Validate the given import declaration name.
269 * The name of an import corresponds to a fully qualified type name or an
270 * on-demand package name as defined by ImportDeclaration (JLS2 7.5). For
271 * example, <code>"java.util.*"</code> or
272 * <code>"java.util.Hashtable"</code>.
275 * the import declaration
276 * @return a status object with code <code>IStatus.OK</code> if the given
277 * name is valid as an import declaration, otherwise a status object
278 * indicating what is wrong with the name
280 public static IStatus validateImportDeclaration(String name) {
281 if (name == null || name.length() == 0) {
282 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util
283 .bind("convention.import.nullImport"), null); //$NON-NLS-1$
285 if (name.charAt(name.length() - 1) == '*') {
286 if (name.charAt(name.length() - 2) == '.') {
287 return validatePackageName(name.substring(0, name.length() - 2));
289 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util
290 .bind("convention.import.unqualifiedImport"), null); //$NON-NLS-1$
293 return validatePackageName(name);
297 * Validate the given Java type name, either simple or qualified. For
298 * example, <code>"java.lang.Object"</code>, or <code>"Object"</code>.
303 * @return a status object with code <code>IStatus.OK</code> if the given
304 * name is valid as a Java type name, a status with code
305 * <code>IStatus.WARNING</code> indicating why the given name is
306 * discouraged, otherwise a status object indicating what is wrong
309 public static IStatus validateJavaTypeName(String name) {
311 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util
312 .bind("convention.type.nullName"), null); //$NON-NLS-1$
314 String trimmed = name.trim();
315 if (!name.equals(trimmed)) {
316 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util
317 .bind("convention.type.nameWithBlanks"), null); //$NON-NLS-1$
319 int index = name.lastIndexOf('.');
323 scannedID = scannedIdentifier(name);
326 String pkg = name.substring(0, index).trim();
327 IStatus status = validatePackageName(pkg);
328 if (!status.isOK()) {
331 String type = name.substring(index + 1).trim();
332 scannedID = scannedIdentifier(type);
335 if (scannedID != null) {
336 IStatus status = ResourcesPlugin.getWorkspace().validateName(
337 new String(scannedID), IResource.FILE);
338 if (!status.isOK()) {
341 if (CharOperation.contains('$', scannedID)) {
342 return new Status(IStatus.WARNING, JavaCore.PLUGIN_ID, -1, Util
343 .bind("convention.type.dollarName"), null); //$NON-NLS-1$
345 if ((scannedID.length > 0 && Character.isLowerCase(scannedID[0]))) {
346 return new Status(IStatus.WARNING, JavaCore.PLUGIN_ID, -1, Util
347 .bind("convention.type.lowercaseName"), null); //$NON-NLS-1$
349 return JavaModelStatus.VERIFIED_OK;
351 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind(
352 "convention.type.invalidName", name), null); //$NON-NLS-1$
357 * Validate the given method name. The special names "<init>" and
358 * "<clinit>" are not valid.
360 * The syntax for a method name is defined by Identifier of MethodDeclarator
361 * (JLS2 8.4). For example "println".
364 * the name of a method
365 * @return a status object with code <code>IStatus.OK</code> if the given
366 * name is valid as a method name, otherwise a status object
367 * indicating what is wrong with the name
369 public static IStatus validateMethodName(String name) {
371 return validateIdentifier(name);
375 * Validate the given package name.
377 * The syntax of a package name corresponds to PackageName as defined by
378 * PackageDeclaration (JLS2 7.4). For example, <code>"java.lang"</code>.
380 * Note that the given name must be a non-empty package name (that is,
381 * attempting to validate the default package will return an error status.)
382 * Also it must not contain any characters or substrings that are not valid
383 * on the file system on which workspace root is located.
386 * the name of a package
387 * @return a status object with code <code>IStatus.OK</code> if the given
388 * name is valid as a package name, otherwise a status object
389 * indicating what is wrong with the name
391 public static IStatus validatePackageName(String name) {
394 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util
395 .bind("convention.package.nullName"), null); //$NON-NLS-1$
398 if ((length = name.length()) == 0) {
399 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util
400 .bind("convention.package.emptyName"), null); //$NON-NLS-1$
402 if (name.charAt(0) == DOT || name.charAt(length - 1) == DOT) {
403 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util
404 .bind("convention.package.dotName"), null); //$NON-NLS-1$
406 if (CharOperation.isWhitespace(name.charAt(0))
407 || CharOperation.isWhitespace(name.charAt(name.length() - 1))) {
408 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util
409 .bind("convention.package.nameWithBlanks"), null); //$NON-NLS-1$
412 while (dot != -1 && dot < length - 1) {
413 if ((dot = name.indexOf(DOT, dot + 1)) != -1 && dot < length - 1
414 && name.charAt(dot + 1) == DOT) {
415 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util
416 .bind("convention.package.consecutiveDotsName"), null); //$NON-NLS-1$
419 IWorkspace workspace = ResourcesPlugin.getWorkspace();
420 StringTokenizer st = new StringTokenizer(name, new String(
421 new char[] { DOT }));
422 boolean firstToken = true;
423 IStatus warningStatus = null;
424 while (st.hasMoreTokens()) {
425 String typeName = st.nextToken();
426 typeName = typeName.trim(); // grammar allows spaces
427 char[] scannedID = scannedIdentifier(typeName);
428 if (scannedID == null) {
429 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util
430 .bind("convention.illegalIdentifier", typeName), null); //$NON-NLS-1$
432 IStatus status = workspace.validateName(new String(scannedID),
434 if (!status.isOK()) {
437 if (firstToken && scannedID.length > 0
438 && Character.isUpperCase(scannedID[0])) {
439 if (warningStatus == null) {
440 warningStatus = new Status(IStatus.WARNING,
441 JavaCore.PLUGIN_ID, -1,
442 Util.bind("convention.package.uppercaseName"), null); //$NON-NLS-1$
447 if (warningStatus != null) {
448 return warningStatus;
450 return JavaModelStatus.VERIFIED_OK;
454 * Validate a given classpath and output location for a project, using the
457 * <li> Classpath entries cannot collide with each other; that is, all entry
458 * paths must be unique.
459 * <li> The project output location path cannot be null, must be absolute
460 * and located inside the project.
461 * <li> Specific output locations (specified on source entries) can be null,
462 * if not they must be located inside the project,
463 * <li> A project entry cannot refer to itself directly (that is, a project
464 * cannot prerequisite itself).
465 * <li> Classpath entries or output locations cannot coincidate or be nested
466 * in each other, except for the following scenarii listed below:
468 * <li> A source folder can coincidate with its own output location, in
469 * which case this output can then contain library archives. However, a
470 * specific output location cannot coincidate with any library or a distinct
471 * source folder than the one referring to it. </li>
472 * <li> A source/library folder can be nested in any source folder as long
473 * as the nested folder is excluded from the enclosing one. </li>
474 * <li> An output location can be nested in a source folder, if the source
475 * folder coincidates with the project itself, or if the output location is
476 * excluded from the source folder.
480 * Note that the classpath entries are not validated automatically. Only
481 * bound variables or containers are considered in the checking process
482 * (this allows to perform a consistency check on a classpath which has
483 * references to yet non existing projects, folders, ...).
485 * This validation is intended to anticipate classpath issues prior to
486 * assigning it to a project. In particular, it will automatically be
487 * performed during the classpath setting operation (if validation fails,
488 * the classpath setting will not complete).
492 * the given java project
493 * @param rawClasspath
494 * the given classpath
495 * @param projectOutputLocation
496 * the given output location
497 * @return a status object with code <code>IStatus.OK</code> if the given
498 * classpath and output location are compatible, otherwise a status
499 * object indicating what is wrong with the classpath or output
503 public static IJavaModelStatus validateClasspath(IJavaProject javaProject,
504 IClasspathEntry[] rawClasspath, IPath projectOutputLocation) {
506 return ClasspathEntry.validateClasspath(javaProject, rawClasspath,
507 projectOutputLocation);
511 * Returns a Java model status describing the problem related to this
512 * classpath entry if any, a status object with code <code>IStatus.OK</code>
513 * if the entry is fine (that is, if the given classpath entry denotes a
514 * valid element to be referenced onto a classpath).
517 * the given java project
519 * the given classpath entry
520 * @param checkSourceAttachment
521 * a flag to determine if source attachement should be checked
522 * @return a java model status describing the problem related to this
523 * classpath entry if any, a status object with code
524 * <code>IStatus.OK</code> if the entry is fine
527 public static IJavaModelStatus validateClasspathEntry(IJavaProject project,
528 IClasspathEntry entry, boolean checkSourceAttachment) {
529 return ClasspathEntry.validateClasspathEntry(project, entry,
530 checkSourceAttachment, true/* recurse in container */);