3e6575feb54156aa2fd57e8ba1a5f05f95f5618c
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / core / JavaConventions.java
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
7  * 
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *******************************************************************************/
11 package net.sourceforge.phpdt.core;
12
13 import java.util.StringTokenizer;
14
15 import org.eclipse.core.resources.IResource;
16 import org.eclipse.core.resources.IWorkspace;
17 import org.eclipse.core.resources.ResourcesPlugin;
18 import org.eclipse.core.runtime.IPath;
19 import org.eclipse.core.runtime.IStatus;
20 import org.eclipse.core.runtime.Status;
21 import net.sourceforge.phpdt.core.compiler.*;
22 import net.sourceforge.phpdt.core.compiler.InvalidInputException;
23 import net.sourceforge.phpdt.internal.compiler.parser.Scanner;
24 import net.sourceforge.phpdt.internal.compiler.util.SuffixConstants;
25 import net.sourceforge.phpdt.internal.core.*;
26 import net.sourceforge.phpdt.internal.core.JavaModelStatus;
27 import net.sourceforge.phpdt.internal.core.util.Util;
28
29 /**
30  * Provides methods for checking Java-specific conventions such as name syntax.
31  * <p>
32  * This class provides static methods and constants only; it is not intended to be
33  * instantiated or subclassed by clients.
34  * </p>
35  */
36 public final class JavaConventions {
37
38         private final static char DOT= '.';
39         private final static Scanner SCANNER = new Scanner();
40
41         private JavaConventions() {
42                 // Not instantiable
43         }
44
45         /**
46          * Returns whether the given package fragment root paths are considered
47          * to overlap.
48          * <p>
49          * Two root paths overlap if one is a prefix of the other, or they point to
50          * the same location. However, a JAR is allowed to be nested in a root.
51          *
52          * @param rootPath1 the first root path
53          * @param rootPath2 the second root path
54          * @return true if the given package fragment root paths are considered to overlap, false otherwise
55          * @deprecated Overlapping roots are allowed in 2.1
56          */
57         public static boolean isOverlappingRoots(IPath rootPath1, IPath rootPath2) {
58                 if (rootPath1 == null || rootPath2 == null) {
59                         return false;
60                 }
61 //              String extension1 = rootPath1.getFileExtension();
62 //              String extension2 = rootPath2.getFileExtension();
63 //              if (extension1 != null && (extension1.equalsIgnoreCase(SuffixConstants.EXTENSION_JAR) || extension1.equalsIgnoreCase(SuffixConstants.EXTENSION_ZIP))) {
64 //                      return false;
65 //              } 
66 //              if (extension2 != null && (extension2.equalsIgnoreCase(SuffixConstants.EXTENSION_JAR) || extension2.equalsIgnoreCase(SuffixConstants.EXTENSION_ZIP))) {
67 //                      return false;
68 //              }
69                 return rootPath1.isPrefixOf(rootPath2) || rootPath2.isPrefixOf(rootPath1);
70         }
71
72         /*
73          * Returns the current identifier extracted by the scanner (without unicode
74          * escapes) from the given id.
75          * Returns <code>null</code> if the id was not valid
76          */
77         private static synchronized char[] scannedIdentifier(String id) {
78                 if (id == null) {
79                         return null;
80                 }
81                 String trimmed = id.trim();
82                 if (!trimmed.equals(id)) {
83                         return null;
84                 }
85                 try {
86                         SCANNER.setSource(id.toCharArray());
87                         int token = SCANNER.getNextToken();
88                         char[] currentIdentifier;
89                         try {
90                                 currentIdentifier = SCANNER.getCurrentIdentifierSource();
91                         } catch (ArrayIndexOutOfBoundsException e) {
92                                 return null;
93                         }
94                         int nextToken= SCANNER.getNextToken();
95                         if (token == ITerminalSymbols.TokenNameIdentifier 
96                                 && nextToken == ITerminalSymbols.TokenNameEOF
97                                 && SCANNER.startPosition == SCANNER.source.length) { // to handle case where we had an ArrayIndexOutOfBoundsException 
98                                                                                                                                      // while reading the last token
99                                 return currentIdentifier;
100                         } else {
101                                 return null;
102                         }
103                 }
104                 catch (InvalidInputException e) {
105                         return null;
106                 }
107         }
108
109         /**
110          * Validate the given compilation unit name.
111          * A compilation unit name must obey the following rules:
112          * <ul>
113          * <li> it must not be null
114          * <li> it must include the <code>".java"</code> suffix
115          * <li> its prefix must be a valid identifier
116          * <li> it must not contain any characters or substrings that are not valid 
117          *                 on the file system on which workspace root is located.
118          * </ul>
119          * </p>
120          * @param name the name of a compilation unit
121          * @return a status object with code <code>IStatus.OK</code> if
122          *              the given name is valid as a compilation unit name, otherwise a status 
123          *              object indicating what is wrong with the name
124          */
125         public static IStatus validateCompilationUnitName(String name) {
126                 if (name == null) {
127                         return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.unit.nullName"), null); //$NON-NLS-1$
128                 }
129                 if (!net.sourceforge.phpdt.internal.compiler.util.Util.isJavaFileName(name)) {
130                         return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.unit.notJavaName"), null); //$NON-NLS-1$
131                 }
132                 String identifier;
133                 int index;
134                 index = name.lastIndexOf('.');
135                 if (index == -1) {
136                         return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.unit.notJavaName"), null); //$NON-NLS-1$
137                 }
138                 identifier = name.substring(0, index);
139                 // JSR-175 metadata strongly recommends "package-info.java" as the
140                 // file in which to store package annotations and
141                 // the package-level spec (replaces package.html)
142                 if (!identifier.equals("package-info")) { //$NON-NLS-1$
143                         IStatus status = validateIdentifier(identifier);
144                         if (!status.isOK()) {
145                                 return status;
146                         }
147                 }
148                 IStatus status = ResourcesPlugin.getWorkspace().validateName(name, IResource.FILE);
149                 if (!status.isOK()) {
150                         return status;
151                 }
152                 return JavaModelStatus.VERIFIED_OK;
153         }
154
155         /**
156          * Validate the given .class file name.
157          * A .class file name must obey the following rules:
158          * <ul>
159          * <li> it must not be null
160          * <li> it must include the <code>".class"</code> suffix
161          * <li> its prefix must be a valid identifier
162          * <li> it must not contain any characters or substrings that are not valid 
163          *                 on the file system on which workspace root is located.
164          * </ul>
165          * </p>
166          * @param name the name of a .class file
167          * @return a status object with code <code>IStatus.OK</code> if
168          *              the given name is valid as a .class file name, otherwise a status 
169          *              object indicating what is wrong with the name
170          * @since 2.0
171          */
172 //      public static IStatus validateClassFileName(String name) {
173 //              if (name == null) {
174 //                      return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.classFile.nullName"), null); //$NON-NLS-1$
175 //              }
176 //              if (!net.sourceforge.phpdt.internal.compiler.util.Util.isClassFileName(name)) {
177 //                      return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.classFile.notClassFileName"), null); //$NON-NLS-1$
178 //              }
179 //              String identifier;
180 //              int index;
181 //              index = name.lastIndexOf('.');
182 //              if (index == -1) {
183 //                      return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.classFile.notClassFileName"), null); //$NON-NLS-1$
184 //              }
185 //              identifier = name.substring(0, index);
186 //              IStatus status = validateIdentifier(identifier);
187 //              if (!status.isOK()) {
188 //                      return status;
189 //              }
190 //              status = ResourcesPlugin.getWorkspace().validateName(name, IResource.FILE);
191 //              if (!status.isOK()) {
192 //                      return status;
193 //              }
194 //              return JavaModelStatus.VERIFIED_OK;
195 //      }
196
197         /**
198          * Validate the given field name.
199          * <p>
200          * Syntax of a field name corresponds to VariableDeclaratorId (JLS2 8.3).
201          * For example, <code>"x"</code>.
202          *
203          * @param name the name of a field
204          * @return a status object with code <code>IStatus.OK</code> if
205          *              the given name is valid as a field name, otherwise a status 
206          *              object indicating what is wrong with the name
207          */
208         public static IStatus validateFieldName(String name) {
209                 return validateIdentifier(name);
210         }
211
212         /**
213          * Validate the given Java identifier.
214          * The identifier must not have the same spelling as a Java keyword,
215          * boolean literal (<code>"true"</code>, <code>"false"</code>), or null literal (<code>"null"</code>).
216          * See section 3.8 of the <em>Java Language Specification, Second Edition</em> (JLS2).
217          * A valid identifier can act as a simple type name, method name or field name.
218          *
219          * @param id the Java identifier
220          * @return a status object with code <code>IStatus.OK</code> if
221          *              the given identifier is a valid Java identifier, otherwise a status 
222          *              object indicating what is wrong with the identifier
223          */
224         public static IStatus validateIdentifier(String id) {
225                 if (scannedIdentifier(id) != null) {
226                         return JavaModelStatus.VERIFIED_OK;
227                 } else {
228                         return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.illegalIdentifier", id), null); //$NON-NLS-1$
229                 }
230         }
231
232         /**
233          * Validate the given import declaration name.
234          * <p>
235          * The name of an import corresponds to a fully qualified type name
236          * or an on-demand package name as defined by ImportDeclaration (JLS2 7.5).
237          * For example, <code>"java.util.*"</code> or <code>"java.util.Hashtable"</code>.
238          *
239          * @param name the import declaration
240          * @return a status object with code <code>IStatus.OK</code> if
241          *              the given name is valid as an import declaration, otherwise a status 
242          *              object indicating what is wrong with the name
243          */
244         public static IStatus validateImportDeclaration(String name) {
245                 if (name == null || name.length() == 0) {
246                         return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.import.nullImport"), null); //$NON-NLS-1$
247                 } 
248                 if (name.charAt(name.length() - 1) == '*') {
249                         if (name.charAt(name.length() - 2) == '.') {
250                                 return validatePackageName(name.substring(0, name.length() - 2));
251                         } else {
252                                 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.import.unqualifiedImport"), null); //$NON-NLS-1$
253                         }
254                 }
255                 return validatePackageName(name);
256         }
257
258         /**
259          * Validate the given Java type name, either simple or qualified.
260          * For example, <code>"java.lang.Object"</code>, or <code>"Object"</code>.
261          * <p>
262          *
263          * @param name the name of a type
264          * @return a status object with code <code>IStatus.OK</code> if
265          *              the given name is valid as a Java type name, 
266          *      a status with code <code>IStatus.WARNING</code>
267          *              indicating why the given name is discouraged, 
268          *      otherwise a status object indicating what is wrong with 
269          *      the name
270          */
271         public static IStatus validateJavaTypeName(String name) {
272                 if (name == null) {
273                         return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.type.nullName"), null); //$NON-NLS-1$
274                 }
275                 String trimmed = name.trim();
276                 if (!name.equals(trimmed)) {
277                         return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.type.nameWithBlanks"), null); //$NON-NLS-1$
278                 }
279                 int index = name.lastIndexOf('.');
280                 char[] scannedID;
281                 if (index == -1) {
282                         // simple name
283                         scannedID = scannedIdentifier(name);
284                 } else {
285                         // qualified name
286                         String pkg = name.substring(0, index).trim();
287                         IStatus status = validatePackageName(pkg);
288                         if (!status.isOK()) {
289                                 return status;
290                         }
291                         String type = name.substring(index + 1).trim();
292                         scannedID = scannedIdentifier(type);
293                 }
294         
295                 if (scannedID != null) {
296                         IStatus status = ResourcesPlugin.getWorkspace().validateName(new String(scannedID), IResource.FILE);
297                         if (!status.isOK()) {
298                                 return status;
299                         }
300                         if (CharOperation.contains('$', scannedID)) {
301                                 return new Status(IStatus.WARNING, JavaCore.PLUGIN_ID, -1, Util.bind("convention.type.dollarName"), null); //$NON-NLS-1$
302                         }
303                         if ((scannedID.length > 0 && Character.isLowerCase(scannedID[0]))) {
304                                 return new Status(IStatus.WARNING, JavaCore.PLUGIN_ID, -1, Util.bind("convention.type.lowercaseName"), null); //$NON-NLS-1$
305                         }
306                         return JavaModelStatus.VERIFIED_OK;
307                 } else {
308                         return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.type.invalidName", name), null); //$NON-NLS-1$
309                 }
310         }
311
312         /**
313          * Validate the given method name.
314          * The special names "&lt;init&gt;" and "&lt;clinit&gt;" are not valid.
315          * <p>
316          * The syntax for a method  name is defined by Identifier
317          * of MethodDeclarator (JLS2 8.4). For example "println".
318          *
319          * @param name the name of a method
320          * @return a status object with code <code>IStatus.OK</code> if
321          *              the given name is valid as a method name, otherwise a status 
322          *              object indicating what is wrong with the name
323          */
324         public static IStatus validateMethodName(String name) {
325
326                 return validateIdentifier(name);
327         }
328
329         /**
330          * Validate the given package name.
331          * <p>
332          * The syntax of a package name corresponds to PackageName as
333          * defined by PackageDeclaration (JLS2 7.4). For example, <code>"java.lang"</code>.
334          * <p>
335          * Note that the given name must be a non-empty package name (that is, attempting to
336          * validate the default package will return an error status.)
337          * Also it must not contain any characters or substrings that are not valid 
338          * on the file system on which workspace root is located.
339          *
340          * @param name the name of a package
341          * @return a status object with code <code>IStatus.OK</code> if
342          *              the given name is valid as a package name, otherwise a status 
343          *              object indicating what is wrong with the name
344          */
345         public static IStatus validatePackageName(String name) {
346
347                 if (name == null) {
348                         return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.package.nullName"), null); //$NON-NLS-1$
349                 }
350                 int length;
351                 if ((length = name.length()) == 0) {
352                         return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.package.emptyName"), null); //$NON-NLS-1$
353                 }
354                 if (name.charAt(0) == DOT || name.charAt(length-1) == DOT) {
355                         return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.package.dotName"), null); //$NON-NLS-1$
356                 }
357                 if (CharOperation.isWhitespace(name.charAt(0)) || CharOperation.isWhitespace(name.charAt(name.length() - 1))) {
358                         return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.package.nameWithBlanks"), null); //$NON-NLS-1$
359                 }
360                 int dot = 0;
361                 while (dot != -1 && dot < length-1) {
362                         if ((dot = name.indexOf(DOT, dot+1)) != -1 && dot < length-1 && name.charAt(dot+1) == DOT) {
363                                 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.package.consecutiveDotsName"), null); //$NON-NLS-1$
364                                 }
365                 }
366                 IWorkspace workspace = ResourcesPlugin.getWorkspace();
367                 StringTokenizer st = new StringTokenizer(name, new String(new char[] {DOT}));
368                 boolean firstToken = true;
369                 IStatus warningStatus = null;
370                 while (st.hasMoreTokens()) {
371                         String typeName = st.nextToken();
372                         typeName = typeName.trim(); // grammar allows spaces
373                         char[] scannedID = scannedIdentifier(typeName);
374                         if (scannedID == null) {
375                                 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.illegalIdentifier", typeName), null); //$NON-NLS-1$
376                         }
377                         IStatus status = workspace.validateName(new String(scannedID), IResource.FOLDER);
378                         if (!status.isOK()) {
379                                 return status;
380                         }
381                         if (firstToken && scannedID.length > 0 && Character.isUpperCase(scannedID[0])) {
382                                 if (warningStatus == null) {
383                                         warningStatus = new Status(IStatus.WARNING, JavaCore.PLUGIN_ID, -1, Util.bind("convention.package.uppercaseName"), null); //$NON-NLS-1$
384                                 }
385                         }
386                         firstToken = false;
387                 }
388                 if (warningStatus != null) {
389                         return warningStatus;
390                 }
391                 return JavaModelStatus.VERIFIED_OK;
392         }
393         
394         /**
395          * Validate a given classpath and output location for a project, using the following rules:
396          * <ul>
397          *   <li> Classpath entries cannot collide with each other; that is, all entry paths must be unique.
398          *   <li> The project output location path cannot be null, must be absolute and located inside the project.
399          *   <li> Specific output locations (specified on source entries) can be null, if not they must be located inside the project,
400          *   <li> A project entry cannot refer to itself directly (that is, a project cannot prerequisite itself).
401      *   <li> Classpath entries or output locations cannot coincidate or be nested in each other, except for the following scenarii listed below:
402          *      <ul><li> A source folder can coincidate with its own output location, in which case this output can then contain library archives. 
403          *                     However, a specific output location cannot coincidate with any library or a distinct source folder than the one referring to it. </li> 
404          *              <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>
405          *                      <li> An output location can be nested in a source folder, if the source folder coincidates with the project itself, or if the output
406          *                                      location is excluded from the source folder.
407          *      </ul>
408          * </ul>
409          * 
410          *  Note that the classpath entries are not validated automatically. Only bound variables or containers are considered 
411          *  in the checking process (this allows to perform a consistency check on a classpath which has references to
412          *  yet non existing projects, folders, ...).
413          *  <p>
414          *  This validation is intended to anticipate classpath issues prior to assigning it to a project. In particular, it will automatically
415          *  be performed during the classpath setting operation (if validation fails, the classpath setting will not complete).
416          *  <p>
417          * @param javaProject the given java project
418          * @param rawClasspath the given classpath
419          * @param projectOutputLocation the given output location
420          * @return a status object with code <code>IStatus.OK</code> if
421          *              the given classpath and output location are compatible, otherwise a status 
422          *              object indicating what is wrong with the classpath or output location
423          * @since 2.0
424          */
425         public static IJavaModelStatus validateClasspath(IJavaProject javaProject, IClasspathEntry[] rawClasspath, IPath projectOutputLocation) {
426
427                 return ClasspathEntry.validateClasspath(javaProject, rawClasspath, projectOutputLocation);
428         }
429         
430         /**
431          * Returns a Java model status describing the problem related to this classpath entry if any, 
432          * a status object with code <code>IStatus.OK</code> if the entry is fine (that is, if the
433          * given classpath entry denotes a valid element to be referenced onto a classpath).
434          * 
435          * @param project the given java project
436          * @param entry the given classpath entry
437          * @param checkSourceAttachment a flag to determine if source attachement should be checked
438          * @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
439          * @since 2.0
440          */
441         public static IJavaModelStatus validateClasspathEntry(IJavaProject project, IClasspathEntry entry, boolean checkSourceAttachment){
442                 return ClasspathEntry.validateClasspathEntry(project, entry, checkSourceAttachment, true/*recurse in container*/);
443         }
444 }