/******************************************************************************* * Copyright (c) 2012-2015 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.ide.ext.java.jdt.core; import org.eclipse.che.ide.ext.java.jdt.core.compiler.CharOperation; import org.eclipse.che.ide.ext.java.jdt.core.compiler.InvalidInputException; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.ClassFileConstants; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.lookup.TypeConstants; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.parser.Scanner; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.parser.ScannerHelper; import org.eclipse.che.ide.ext.java.jdt.internal.compiler.parser.TerminalTokens; import org.eclipse.che.ide.runtime.IStatus; import org.eclipse.che.ide.runtime.Status; import org.eclipse.che.ide.util.NameUtils; /** @author Evgen Vidolob */ public class JavaConventions { private static final char DOT = '.'; private static final String PACKAGE_INFO = new String(TypeConstants.PACKAGE_INFO_NAME); private static final Scanner SCANNER = new Scanner(false /*comment*/, true /*whitespace*/, false /*nls*/, ClassFileConstants.JDK1_3 /*sourceLevel*/, null/*taskTag*/, null/*taskPriorities*/, true /*taskCaseSensitive*/); private static final IStatus VERIFIED_OK = new Status(IStatus.OK, JavaCore.PLUGIN_ID, "ok"); /** * Validate the given package name for the given source and compliance levels. * <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 * @param sourceLevel * the source level * @param complianceLevel * the compliance level * @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 * @since 3.3 */ public static IStatus validatePackageName(String name, String sourceLevel, String complianceLevel) { if (name == null) { return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Messages.INSTANCE.convention_package_nullName(), null); } if (!NameUtils.checkFileName(name)) { //TODO: not correct in Java world but fix problem described in IDEX-1841 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, "Package name not valid (only latin and digits)", null); } int length; if ((length = name.length()) == 0) { return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Messages.INSTANCE.convention_package_emptyName(), null); } if (name.charAt(0) == DOT || name.charAt(length - 1) == DOT) { return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Messages.INSTANCE.convention_package_dotName(), null); } if (CharOperation.isWhitespace(name.charAt(0)) || CharOperation.isWhitespace(name.charAt(name.length() - 1))) { return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Messages.INSTANCE.convention_package_nameWithBlanks(), null); } 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, Messages.INSTANCE.convention_package_consecutiveDotsName(), null); } } // IWorkspace workspace = ResourcesPlugin.getWorkspace(); // StringTokenizer st = new StringTokenizer(name, "."); //$NON-NLS-1$ String[] split = name.split("\\."); boolean firstToken = true; IStatus warningStatus = null; for (String typeName : split) { // String typeName = st.nextToken(); typeName = typeName.trim(); // grammar allows spaces char[] scannedID = scannedIdentifier(typeName, sourceLevel, complianceLevel); if (scannedID == null) { return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, Messages.INSTANCE.convention_illegalIdentifier(typeName), null); } // IStatus status = workspace.validateName(new String(scannedID), IResource.FOLDER); // if (!status.isOK()) // { // return status; // } if (firstToken && scannedID.length > 0 && ScannerHelper.isUpperCase(scannedID[0])) { if (warningStatus == null) { warningStatus = new Status(IStatus.WARNING, JavaCore.PLUGIN_ID, -1, Messages.INSTANCE.convention_package_uppercaseName(), null); } } firstToken = false; } if (warningStatus != null) { return warningStatus; } return VERIFIED_OK; } /* * Returns the current identifier extracted by the scanner (without unicode * escapes) from the given id and for the given source and compliance levels. * Returns <code>null</code> if the id was not valid */ private static char[] scannedIdentifier(String id, String sourceLevel, String complianceLevel) { if (id == null) { return null; } // Set scanner for given source and compliance levels SCANNER.sourceLevel = sourceLevel == null ? ClassFileConstants.JDK1_3 : CompilerOptions.versionToJdkLevel(sourceLevel); SCANNER.complianceLevel = complianceLevel == null ? ClassFileConstants.JDK1_3 : CompilerOptions.versionToJdkLevel(complianceLevel); try { SCANNER.setSource(id.toCharArray()); int token = SCANNER.scanIdentifier(); if (token != TerminalTokens.TokenNameIdentifier) return null; if (SCANNER.currentPosition == SCANNER.eofPosition) { // to handle case where we had an ArrayIndexOutOfBoundsException try { return SCANNER.getCurrentIdentifierSource(); } catch (ArrayIndexOutOfBoundsException e) { return null; } } else { return null; } } catch (InvalidInputException e) { return null; } } /** * Validate the given compilation unit name for the given source and compliance levels. * <p> * A compilation unit name must obey the following rules: * <ul> * <li> it must not be null * <li> it must be suffixed by a dot ('.') followed by one of the * {@link JavaCore#getJavaLikeExtensions() Java-like extensions} * <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 * @param sourceLevel * the source level * @param complianceLevel * the compliance level * @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 * @since 3.3 */ public static IStatus validateCompilationUnitName(String name, String sourceLevel, String complianceLevel) { if (name == null) { return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, "Compilation unit name must not be null", null); } if (!NameUtils.checkFileName(name)) { //TODO: not correct in Java world but fix problem described in IDEX-1841 return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, "Compilation unit name not valid (only latin and digits)", null); } String message = "Compilation unit name must end with .java, or one of the registered Java-like extensions"; if (!org.eclipse.che.ide.ext.java.jdt.internal.core.util.Util.isJavaLikeFileName(name)) { return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, message, null); } String identifier; int index; index = name.lastIndexOf('.'); if (index == -1) { return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, message, null); } 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)) { IStatus status = validateIdentifier(identifier, sourceLevel, complianceLevel); if (!status.isOK()) { return status; } } // IStatus status = ResourcesPlugin.getWorkspace().validateName(name, IResource.FILE); // if (!status.isOK()) { // return status; // } return Status.OK_STATUS; } /** * Validate the given Java identifier for the given source and compliance levels * 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 * @param sourceLevel * the source level * @param complianceLevel * the compliance level * @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 * @since 3.3 */ public static IStatus validateIdentifier(String id, String sourceLevel, String complianceLevel) { if (scannedIdentifier(id, sourceLevel, complianceLevel) != null) { return Status.OK_STATUS; } else { return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, -1, id + " is not a valid Java identifier", null); } } /** * Validate the given method name for the given source and compliance levels. * 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 * @param sourceLevel * the source level * @param complianceLevel * the compliance level * @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 * @since 3.3 */ public static IStatus validateMethodName(String name, String sourceLevel, String complianceLevel) { return validateIdentifier(name, sourceLevel, complianceLevel); } }