Organized imports
[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 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;
22
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;
29
30 /**
31  * Provides methods for checking Java-specific conventions such as name syntax.
32  * <p>
33  * This class provides static methods and constants only; it is not intended to be
34  * instantiated or subclassed by clients.
35  * </p>
36  */
37 public final class JavaConventions {
38
39         private final static char DOT= '.';
40         private final static Scanner SCANNER = new Scanner();
41
42         private JavaConventions() {
43                 // Not instantiable
44         }
45
46         /**
47          * Returns whether the given package fragment root paths are considered
48          * to overlap.
49          * <p>
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.
52          *
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
57          */
58         public static boolean isOverlappingRoots(IPath rootPath1, IPath rootPath2) {
59                 if (rootPath1 == null || rootPath2 == null) {
60                         return false;
61                 }
62 //              String extension1 = rootPath1.getFileExtension();
63 //              String extension2 = rootPath2.getFileExtension();
64 //              if (extension1 != null && (extension1.equalsIgnoreCase(SuffixConstants.EXTENSION_JAR) || extension1.equalsIgnoreCase(SuffixConstants.EXTENSION_ZIP))) {
65 //                      return false;
66 //              } 
67 //              if (extension2 != null && (extension2.equalsIgnoreCase(SuffixConstants.EXTENSION_JAR) || extension2.equalsIgnoreCase(SuffixConstants.EXTENSION_ZIP))) {
68 //                      return false;
69 //              }
70                 return rootPath1.isPrefixOf(rootPath2) || rootPath2.isPrefixOf(rootPath1);
71         }
72
73         /*
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
77          */
78         private static synchronized char[] scannedIdentifier(String id) {
79                 if (id == null) {
80                         return null;
81                 }
82                 String trimmed = id.trim();
83                 if (!trimmed.equals(id)) {
84                         return null;
85                 }
86                 try {
87                         SCANNER.setSource(id.toCharArray());
88                         int token = SCANNER.getNextToken();
89                         char[] currentIdentifier;
90                         try {
91                                 currentIdentifier = SCANNER.getCurrentIdentifierSource();
92                         } catch (ArrayIndexOutOfBoundsException e) {
93                                 return null;
94                         }
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;
101                         } else {
102                                 return null;
103                         }
104                 }
105                 catch (InvalidInputException e) {
106                         return null;
107                 }
108         }
109
110         /**
111          * Validate the given compilation unit name.
112          * A compilation unit name must obey the following rules:
113          * <ul>
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.
119          * </ul>
120          * </p>
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
125          */
126         public static IStatus validateCompilationUnitName(String name) {
127                 if (name == null) {
128                         return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.unit.nullName"), null); //$NON-NLS-1$
129                 }
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$
132                 }
133                 String identifier;
134                 int index;
135                 index = name.lastIndexOf('.');
136                 if (index == -1) {
137                         return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.unit.notJavaName"), null); //$NON-NLS-1$
138                 }
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()) {
146                                 return status;
147                         }
148                 }
149                 IStatus status = ResourcesPlugin.getWorkspace().validateName(name, IResource.FILE);
150                 if (!status.isOK()) {
151                         return status;
152                 }
153                 return JavaModelStatus.VERIFIED_OK;
154         }
155
156         /**
157          * Validate the given .class file name.
158          * A .class file name must obey the following rules:
159          * <ul>
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.
165          * </ul>
166          * </p>
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
171          * @since 2.0
172          */
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$
176 //              }
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$
179 //              }
180 //              String identifier;
181 //              int index;
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$
185 //              }
186 //              identifier = name.substring(0, index);
187 //              IStatus status = validateIdentifier(identifier);
188 //              if (!status.isOK()) {
189 //                      return status;
190 //              }
191 //              status = ResourcesPlugin.getWorkspace().validateName(name, IResource.FILE);
192 //              if (!status.isOK()) {
193 //                      return status;
194 //              }
195 //              return JavaModelStatus.VERIFIED_OK;
196 //      }
197
198         /**
199          * Validate the given field name.
200          * <p>
201          * Syntax of a field name corresponds to VariableDeclaratorId (JLS2 8.3).
202          * For example, <code>"x"</code>.
203          *
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
208          */
209         public static IStatus validateFieldName(String name) {
210                 return validateIdentifier(name);
211         }
212
213         /**
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.
219          *
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
224          */
225         public static IStatus validateIdentifier(String id) {
226                 if (scannedIdentifier(id) != null) {
227                         return JavaModelStatus.VERIFIED_OK;
228                 } else {
229                         return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.illegalIdentifier", id), null); //$NON-NLS-1$
230                 }
231         }
232
233         /**
234          * Validate the given import declaration name.
235          * <p>
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>.
239          *
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
244          */
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$
248                 } 
249                 if (name.charAt(name.length() - 1) == '*') {
250                         if (name.charAt(name.length() - 2) == '.') {
251                                 return validatePackageName(name.substring(0, name.length() - 2));
252                         } else {
253                                 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.import.unqualifiedImport"), null); //$NON-NLS-1$
254                         }
255                 }
256                 return validatePackageName(name);
257         }
258
259         /**
260          * Validate the given Java type name, either simple or qualified.
261          * For example, <code>"java.lang.Object"</code>, or <code>"Object"</code>.
262          * <p>
263          *
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 
270          *      the name
271          */
272         public static IStatus validateJavaTypeName(String name) {
273                 if (name == null) {
274                         return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.type.nullName"), null); //$NON-NLS-1$
275                 }
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$
279                 }
280                 int index = name.lastIndexOf('.');
281                 char[] scannedID;
282                 if (index == -1) {
283                         // simple name
284                         scannedID = scannedIdentifier(name);
285                 } else {
286                         // qualified name
287                         String pkg = name.substring(0, index).trim();
288                         IStatus status = validatePackageName(pkg);
289                         if (!status.isOK()) {
290                                 return status;
291                         }
292                         String type = name.substring(index + 1).trim();
293                         scannedID = scannedIdentifier(type);
294                 }
295         
296                 if (scannedID != null) {
297                         IStatus status = ResourcesPlugin.getWorkspace().validateName(new String(scannedID), IResource.FILE);
298                         if (!status.isOK()) {
299                                 return status;
300                         }
301                         if (CharOperation.contains('$', scannedID)) {
302                                 return new Status(IStatus.WARNING, JavaCore.PLUGIN_ID, -1, Util.bind("convention.type.dollarName"), null); //$NON-NLS-1$
303                         }
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$
306                         }
307                         return JavaModelStatus.VERIFIED_OK;
308                 } else {
309                         return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.type.invalidName", name), null); //$NON-NLS-1$
310                 }
311         }
312
313         /**
314          * Validate the given method name.
315          * The special names "&lt;init&gt;" and "&lt;clinit&gt;" are not valid.
316          * <p>
317          * The syntax for a method  name is defined by Identifier
318          * of MethodDeclarator (JLS2 8.4). For example "println".
319          *
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
324          */
325         public static IStatus validateMethodName(String name) {
326
327                 return validateIdentifier(name);
328         }
329
330         /**
331          * Validate the given package name.
332          * <p>
333          * The syntax of a package name corresponds to PackageName as
334          * defined by PackageDeclaration (JLS2 7.4). For example, <code>"java.lang"</code>.
335          * <p>
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.
340          *
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
345          */
346         public static IStatus validatePackageName(String name) {
347
348                 if (name == null) {
349                         return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Util.bind("convention.package.nullName"), null); //$NON-NLS-1$
350                 }
351                 int length;
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$
354                 }
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$
357                 }
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$
360                 }
361                 int dot = 0;
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$
365                                 }
366                 }
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$
377                         }
378                         IStatus status = workspace.validateName(new String(scannedID), IResource.FOLDER);
379                         if (!status.isOK()) {
380                                 return status;
381                         }
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$
385                                 }
386                         }
387                         firstToken = false;
388                 }
389                 if (warningStatus != null) {
390                         return warningStatus;
391                 }
392                 return JavaModelStatus.VERIFIED_OK;
393         }
394         
395         /**
396          * Validate a given classpath and output location for a project, using the following rules:
397          * <ul>
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.
408          *      </ul>
409          * </ul>
410          * 
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, ...).
414          *  <p>
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).
417          *  <p>
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
424          * @since 2.0
425          */
426         public static IJavaModelStatus validateClasspath(IJavaProject javaProject, IClasspathEntry[] rawClasspath, IPath projectOutputLocation) {
427
428                 return ClasspathEntry.validateClasspath(javaProject, rawClasspath, projectOutputLocation);
429         }
430         
431         /**
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).
435          * 
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
440          * @since 2.0
441          */
442         public static IJavaModelStatus validateClasspathEntry(IJavaProject project, IClasspathEntry entry, boolean checkSourceAttachment){
443                 return ClasspathEntry.validateClasspathEntry(project, entry, checkSourceAttachment, true/*recurse in container*/);
444         }
445 }