| /******************************************************************************* |
| * Copyright (c) 2000, 2004 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Common Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/cpl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.core; |
| |
| import java.util.StringTokenizer; |
| |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IWorkspace; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.jdt.core.compiler.*; |
| import org.eclipse.jdt.core.compiler.InvalidInputException; |
| import org.eclipse.jdt.internal.compiler.parser.Scanner; |
| import org.eclipse.jdt.internal.compiler.parser.TerminalTokens; |
| import org.eclipse.jdt.internal.compiler.util.SuffixConstants; |
| import org.eclipse.jdt.internal.core.*; |
| import org.eclipse.jdt.internal.core.JavaModelStatus; |
| import org.eclipse.jdt.internal.core.util.Util; |
| |
| /** |
| * Provides methods for checking Java-specific conventions such as name syntax. |
| * <p> |
| * This class provides static methods and constants only; it is not intended to be |
| * instantiated or subclassed by clients. |
| * </p> |
| */ |
| public final class JavaConventions { |
| |
| private final static char DOT= '.'; |
| private final static Scanner SCANNER = new Scanner(); |
| |
| private JavaConventions() { |
| // Not instantiable |
| } |
| |
| /** |
| * Returns whether the given package fragment root paths are considered |
| * to overlap. |
| * <p> |
| * Two root paths overlap if one is a prefix of the other, or they point to |
| * the same location. However, a JAR is allowed to be nested in a root. |
| * |
| * @param rootPath1 the first root path |
| * @param rootPath2 the second root path |
| * @return true if the given package fragment root paths are considered to overlap, false otherwise |
| * @deprecated Overlapping roots are allowed in 2.1 |
| */ |
| public static boolean isOverlappingRoots(IPath rootPath1, IPath rootPath2) { |
| if (rootPath1 == null || rootPath2 == null) { |
| return false; |
| } |
| String extension1 = rootPath1.getFileExtension(); |
| String extension2 = rootPath2.getFileExtension(); |
| if (extension1 != null && (extension1.equalsIgnoreCase(SuffixConstants.EXTENSION_JAR) || extension1.equalsIgnoreCase(SuffixConstants.EXTENSION_ZIP))) { |
| return false; |
| } |
| if (extension2 != null && (extension2.equalsIgnoreCase(SuffixConstants.EXTENSION_JAR) || extension2.equalsIgnoreCase(SuffixConstants.EXTENSION_ZIP))) { |
| return false; |
| } |
| return rootPath1.isPrefixOf(rootPath2) || rootPath2.isPrefixOf(rootPath1); |
| } |
| |
| /* |
| * Returns the current identifier extracted by the scanner (without unicode |
| * escapes) from the given id. |
| * Returns <code>null</code> if the id was not valid |
| */ |
| private static synchronized char[] scannedIdentifier(String id) { |
| if (id == null) { |
| return null; |
| } |
| String trimmed = id.trim(); |
| if (!trimmed.equals(id)) { |
| return null; |
| } |
| try { |
| SCANNER.setSource(id.toCharArray()); |
| int token = SCANNER.getNextToken(); |
| char[] currentIdentifier; |
| try { |
| currentIdentifier = SCANNER.getCurrentIdentifierSource(); |
| } catch (ArrayIndexOutOfBoundsException e) { |
| return null; |
| } |
| int nextToken= SCANNER.getNextToken(); |
| if (token == TerminalTokens.TokenNameIdentifier |
| && nextToken == TerminalTokens.TokenNameEOF |
| && SCANNER.startPosition == SCANNER.source.length) { // to handle case where we had an ArrayIndexOutOfBoundsException |
| // while reading the last token |
| return currentIdentifier; |
| } else { |
| return null; |
| } |
| } |
| catch (InvalidInputException e) { |
| return null; |
| } |
| } |
| |
| /** |
| * Validate the given compilation unit name. |
| * A compilation unit name must obey the following rules: |
| * <ul> |
| * <li> it must not be null |
| * <li> it must include the <code>".java"</code> suffix |
| * <li> its prefix must be a valid identifier |
| * <li> it must not contain any characters or substrings that are not valid |
| * on the file system on which workspace root is located. |
| * </ul> |
| * </p> |
| * @param name the name of a compilation unit |
| * @return a status object with code <code>IStatus.OK</code> if |
| * the given name is valid as a compilation unit name, otherwise a status |
| * object indicating what is wrong with the name |
| */ |
| public static IStatus validateCompilationUnitName(String name) { |
| if (name == null) { |
| return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.unit.nullName"), null); //$NON-NLS-1$ |
| } |
| if (!org.eclipse.jdt.internal.compiler.util.Util.isJavaFileName(name)) { |
| return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.unit.notJavaName"), null); //$NON-NLS-1$ |
| } |
| String identifier; |
| int index; |
| index = name.lastIndexOf('.'); |
| if (index == -1) { |
| return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.unit.notJavaName"), null); //$NON-NLS-1$ |
| } |
| identifier = name.substring(0, index); |
| // JSR-175 metadata strongly recommends "package-info.java" as the |
| // file in which to store package annotations and |
| // the package-level spec (replaces package.html) |
| if (!identifier.equals("package-info")) { //$NON-NLS-1$ |
| IStatus status = validateIdentifier(identifier); |
| if (!status.isOK()) { |
| return status; |
| } |
| } |
| IStatus status = ResourcesPlugin.getWorkspace().validateName(name, IResource.FILE); |
| if (!status.isOK()) { |
| return status; |
| } |
| return JavaModelStatus.VERIFIED_OK; |
| } |
| |
| /** |
| * Validate the given .class file name. |
| * A .class file name must obey the following rules: |
| * <ul> |
| * <li> it must not be null |
| * <li> it must include the <code>".class"</code> suffix |
| * <li> its prefix must be a valid identifier |
| * <li> it must not contain any characters or substrings that are not valid |
| * on the file system on which workspace root is located. |
| * </ul> |
| * </p> |
| * @param name the name of a .class file |
| * @return a status object with code <code>IStatus.OK</code> if |
| * the given name is valid as a .class file name, otherwise a status |
| * object indicating what is wrong with the name |
| * @since 2.0 |
| */ |
| public static IStatus validateClassFileName(String name) { |
| if (name == null) { |
| return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.classFile.nullName"), null); //$NON-NLS-1$ |
| } |
| if (!org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(name)) { |
| return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.classFile.notClassFileName"), null); //$NON-NLS-1$ |
| } |
| String identifier; |
| int index; |
| index = name.lastIndexOf('.'); |
| if (index == -1) { |
| return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.classFile.notClassFileName"), null); //$NON-NLS-1$ |
| } |
| identifier = name.substring(0, index); |
| IStatus status = validateIdentifier(identifier); |
| if (!status.isOK()) { |
| return status; |
| } |
| status = ResourcesPlugin.getWorkspace().validateName(name, IResource.FILE); |
| if (!status.isOK()) { |
| return status; |
| } |
| return JavaModelStatus.VERIFIED_OK; |
| } |
| |
| /** |
| * Validate the given field name. |
| * <p> |
| * Syntax of a field name corresponds to VariableDeclaratorId (JLS2 8.3). |
| * For example, <code>"x"</code>. |
| * |
| * @param name the name of a field |
| * @return a status object with code <code>IStatus.OK</code> if |
| * the given name is valid as a field name, otherwise a status |
| * object indicating what is wrong with the name |
| */ |
| public static IStatus validateFieldName(String name) { |
| return validateIdentifier(name); |
| } |
| |
| /** |
| * Validate the given Java identifier. |
| * The identifier must not have the same spelling as a Java keyword, |
| * boolean literal (<code>"true"</code>, <code>"false"</code>), or null literal (<code>"null"</code>). |
| * See section 3.8 of the <em>Java Language Specification, Second Edition</em> (JLS2). |
| * A valid identifier can act as a simple type name, method name or field name. |
| * |
| * @param id the Java identifier |
| * @return a status object with code <code>IStatus.OK</code> if |
| * the given identifier is a valid Java identifier, otherwise a status |
| * object indicating what is wrong with the identifier |
| */ |
| public static IStatus validateIdentifier(String id) { |
| if (scannedIdentifier(id) != null) { |
| return JavaModelStatus.VERIFIED_OK; |
| } else { |
| return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.illegalIdentifier", id), null); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * Validate the given import declaration name. |
| * <p> |
| * The name of an import corresponds to a fully qualified type name |
| * or an on-demand package name as defined by ImportDeclaration (JLS2 7.5). |
| * For example, <code>"java.util.*"</code> or <code>"java.util.Hashtable"</code>. |
| * |
| * @param name the import declaration |
| * @return a status object with code <code>IStatus.OK</code> if |
| * the given name is valid as an import declaration, otherwise a status |
| * object indicating what is wrong with the name |
| */ |
| public static IStatus validateImportDeclaration(String name) { |
| if (name == null || name.length() == 0) { |
| return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.import.nullImport"), null); //$NON-NLS-1$ |
| } |
| if (name.charAt(name.length() - 1) == '*') { |
| if (name.charAt(name.length() - 2) == '.') { |
| return validatePackageName(name.substring(0, name.length() - 2)); |
| } else { |
| return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.import.unqualifiedImport"), null); //$NON-NLS-1$ |
| } |
| } |
| return validatePackageName(name); |
| } |
| |
| /** |
| * Validate the given Java type name, either simple or qualified. |
| * For example, <code>"java.lang.Object"</code>, or <code>"Object"</code>. |
| * <p> |
| * |
| * @param name the name of a type |
| * @return a status object with code <code>IStatus.OK</code> if |
| * the given name is valid as a Java type name, |
| * a status with code <code>IStatus.WARNING</code> |
| * indicating why the given name is discouraged, |
| * otherwise a status object indicating what is wrong with |
| * the name |
| */ |
| public static IStatus validateJavaTypeName(String name) { |
| if (name == null) { |
| return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.type.nullName"), null); //$NON-NLS-1$ |
| } |
| String trimmed = name.trim(); |
| if (!name.equals(trimmed)) { |
| return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.type.nameWithBlanks"), null); //$NON-NLS-1$ |
| } |
| int index = name.lastIndexOf('.'); |
| char[] scannedID; |
| if (index == -1) { |
| // simple name |
| scannedID = scannedIdentifier(name); |
| } else { |
| // qualified name |
| String pkg = name.substring(0, index).trim(); |
| IStatus status = validatePackageName(pkg); |
| if (!status.isOK()) { |
| return status; |
| } |
| String type = name.substring(index + 1).trim(); |
| scannedID = scannedIdentifier(type); |
| } |
| |
| if (scannedID != null) { |
| IStatus status = ResourcesPlugin.getWorkspace().validateName(new String(scannedID), IResource.FILE); |
| if (!status.isOK()) { |
| return status; |
| } |
| if (CharOperation.contains('$', scannedID)) { |
| return new Status(IStatus.WARNING, JavaCore.PLUGIN_ID, -1, Util.bind("convention.type.dollarName"), null); //$NON-NLS-1$ |
| } |
| if ((scannedID.length > 0 && Character.isLowerCase(scannedID[0]))) { |
| return new Status(IStatus.WARNING, JavaCore.PLUGIN_ID, -1, Util.bind("convention.type.lowercaseName"), null); //$NON-NLS-1$ |
| } |
| return JavaModelStatus.VERIFIED_OK; |
| } else { |
| return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.type.invalidName", name), null); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * Validate the given method name. |
| * The special names "<init>" and "<clinit>" are not valid. |
| * <p> |
| * The syntax for a method name is defined by Identifier |
| * of MethodDeclarator (JLS2 8.4). For example "println". |
| * |
| * @param name the name of a method |
| * @return a status object with code <code>IStatus.OK</code> if |
| * the given name is valid as a method name, otherwise a status |
| * object indicating what is wrong with the name |
| */ |
| public static IStatus validateMethodName(String name) { |
| |
| return validateIdentifier(name); |
| } |
| |
| /** |
| * Validate the given package name. |
| * <p> |
| * The syntax of a package name corresponds to PackageName as |
| * defined by PackageDeclaration (JLS2 7.4). For example, <code>"java.lang"</code>. |
| * <p> |
| * Note that the given name must be a non-empty package name (that is, attempting to |
| * validate the default package will return an error status.) |
| * Also it must not contain any characters or substrings that are not valid |
| * on the file system on which workspace root is located. |
| * |
| * @param name the name of a package |
| * @return a status object with code <code>IStatus.OK</code> if |
| * the given name is valid as a package name, otherwise a status |
| * object indicating what is wrong with the name |
| */ |
| public static IStatus validatePackageName(String name) { |
| |
| if (name == null) { |
| return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.package.nullName"), null); //$NON-NLS-1$ |
| } |
| int length; |
| if ((length = name.length()) == 0) { |
| return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.package.emptyName"), null); //$NON-NLS-1$ |
| } |
| if (name.charAt(0) == DOT || name.charAt(length-1) == DOT) { |
| return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.package.dotName"), null); //$NON-NLS-1$ |
| } |
| if (CharOperation.isWhitespace(name.charAt(0)) || CharOperation.isWhitespace(name.charAt(name.length() - 1))) { |
| return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.package.nameWithBlanks"), null); //$NON-NLS-1$ |
| } |
| int dot = 0; |
| while (dot != -1 && dot < length-1) { |
| if ((dot = name.indexOf(DOT, dot+1)) != -1 && dot < length-1 && name.charAt(dot+1) == DOT) { |
| return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.package.consecutiveDotsName"), null); //$NON-NLS-1$ |
| } |
| } |
| IWorkspace workspace = ResourcesPlugin.getWorkspace(); |
| StringTokenizer st = new StringTokenizer(name, new String(new char[] {DOT})); |
| boolean firstToken = true; |
| while (st.hasMoreTokens()) { |
| String typeName = st.nextToken(); |
| typeName = typeName.trim(); // grammar allows spaces |
| char[] scannedID = scannedIdentifier(typeName); |
| if (scannedID == null) { |
| return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.illegalIdentifier", typeName), null); //$NON-NLS-1$ |
| } |
| IStatus status = workspace.validateName(new String(scannedID), IResource.FOLDER); |
| if (!status.isOK()) { |
| return status; |
| } |
| if (firstToken && scannedID.length > 0 && Character.isUpperCase(scannedID[0])) { |
| return new Status(IStatus.WARNING, JavaCore.PLUGIN_ID, -1, Util.bind("convention.package.uppercaseName"), null); //$NON-NLS-1$ |
| } |
| firstToken = false; |
| } |
| return JavaModelStatus.VERIFIED_OK; |
| } |
| |
| /** |
| * Validate a given classpath and output location for a project, using the following rules: |
| * <ul> |
| * <li> Classpath entries cannot collide with each other; that is, all entry paths must be unique. |
| * <li> The project output location path cannot be null, must be absolute and located inside the project. |
| * <li> Specific output locations (specified on source entries) can be null, if not they must be located inside the project, |
| * <li> A project entry cannot refer to itself directly (that is, a project cannot prerequisite itself). |
| * <li> Classpath entries or output locations cannot coincidate or be nested in each other, except for the following scenarii listed below: |
| * <ul><li> A source folder can coincidate with its own output location, in which case this output can then contain library archives. |
| * However, a specific output location cannot coincidate with any library or a distinct source folder than the one referring to it. </li> |
| * <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> |
| * <li> An output location can be nested in a source folder, if the source folder coincidates with the project itself, or if the output |
| * location is excluded from the source folder. |
| * </ul> |
| * </ul> |
| * |
| * Note that the classpath entries are not validated automatically. Only bound variables or containers are considered |
| * in the checking process (this allows to perform a consistency check on a classpath which has references to |
| * yet non existing projects, folders, ...). |
| * <p> |
| * This validation is intended to anticipate classpath issues prior to assigning it to a project. In particular, it will automatically |
| * be performed during the classpath setting operation (if validation fails, the classpath setting will not complete). |
| * <p> |
| * @param javaProject the given java project |
| * @param rawClasspath the given classpath |
| * @param projectOutputLocation the given output location |
| * @return a status object with code <code>IStatus.OK</code> if |
| * the given classpath and output location are compatible, otherwise a status |
| * object indicating what is wrong with the classpath or output location |
| * @since 2.0 |
| */ |
| public static IJavaModelStatus validateClasspath(IJavaProject javaProject, IClasspathEntry[] rawClasspath, IPath projectOutputLocation) { |
| |
| return ClasspathEntry.validateClasspath(javaProject, rawClasspath, projectOutputLocation); |
| } |
| |
| /** |
| * Returns 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 (that is, if the |
| * given classpath entry denotes a valid element to be referenced onto a classpath). |
| * |
| * @param project the given java project |
| * @param entry the given classpath entry |
| * @param checkSourceAttachment a flag to determine if source attachement should be checked |
| * @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 |
| * @since 2.0 |
| */ |
| public static IJavaModelStatus validateClasspathEntry(IJavaProject project, IClasspathEntry entry, boolean checkSourceAttachment){ |
| return ClasspathEntry.validateClasspathEntry(project, entry, checkSourceAttachment, true/*recurse in container*/); |
| } |
| } |