Java tutorial
/** * Copyright (c) 2009--2014 Red Hat, Inc. * * This software is licensed to you under the GNU General Public License, * version 2 (GPLv2). There is NO WARRANTY for this software, express or * implied, including the implied warranties of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 * along with this software; if not, see * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. * * Red Hat trademarks are not licensed under GPLv2. No permission is * granted to use or replicate Red Hat trademarks that are incorporated * in this software or its documentation. */ package com.redhat.rhn.common.util; import java.io.File; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.apache.xml.utils.XMLChar; import org.stringtree.json.JSONReader; import org.stringtree.json.JSONWriter; import com.redhat.rhn.common.localization.LocalizationService; import com.redhat.rhn.common.validator.ValidatorException; /** * A simple class that assists with String manipulation * @version $Rev$ */ public class StringUtil { /** * Script check errors. */ public static enum ScriptCheckResult { NO_SHELL_DEFINED(1, "stringutil.scriptcheck.error.noshelldefined"), NO_SCRIPT_DEFINED(2, "stringutil.scriptcheck.error.noscript"); private final int id; private final String messageKey; ScriptCheckResult(int msgId, String message) { this.id = msgId; this.messageKey = message; } /** * Get error ID. * @return int Return check restult ID. */ public int getId() { return id; } /** * Get message key. * @return string Returns message key for the translation. */ public String getMessageKey() { return messageKey; } } /** time-interval formatting selectors */ public static final int SECONDS_UNITS = 0; public static final int MINUTES_UNITS = 1; public static final int HOURS_UNITS = 2; public static final int DAYS_UNITS = 3; public static final int WEEKS_UNITS = 4; public static final int MONTHS_UNITS = 5; public static final int YEARS_UNITS = 6; // Millis-per-time-unit; used by interval-formatting. // Index matches one of the _UNITS above - MUST BE KEPT IN SYNCH static final long[] MILLIS_PER_UNIT = { 1000, 60000, 3600000, 86400000, 604800000, 2419200000L, 29030400000L }; // Time-unit in next-higher unit (ie, 60 secs in 1 minute); used by // interval-formatting. // Index matches _UNITS - MUST BE KEPT IN SYNCH static final long[] UNITS_PER_NEXT = { 60, 60, 24, 7, 4, 12 }; /** * Logger for this class */ private static Logger logger = Logger.getLogger(StringUtil.class); /** * Private constructore */ private StringUtil() { } /** * Convert the passed in string to a valid java method name. This basically * capitalizes each word and removes all word delimiters. * @param strIn The string to convert * @return The converted string */ public static String beanify(String strIn) { String str = strIn.trim(); StringBuilder result = new StringBuilder(str.length()); boolean wasWhitespace = false; for (int i = 0, j = 0; i < str.length(); i++) { char c = str.charAt(i); if (Character.isLetterOrDigit(c)) { if (wasWhitespace) { c = Character.toUpperCase(c); wasWhitespace = false; } result.insert(j, c); j++; continue; } wasWhitespace = true; } return result.toString(); } /** * Convert the passed in bean style string to a underscore separated string. * * For example: someFieldName -> some_field_name * * @param strIn The string to convert * @return The converted string */ public static String debeanify(String strIn) { String str = strIn.trim(); StringBuilder result = new StringBuilder(str.length()); for (int i = 0; i < str.length(); i++) { char c = str.charAt(i); if (Character.isUpperCase(c)) { result.append("_"); } result.append(Character.toLowerCase(c)); } return result.toString(); } /** * Converts the passed in string to a valid java Class name. This basically * capitalizes each word and removes all word delimiters. * @param strIn The string to convert. * @return The converted string. */ public static String classify(String strIn) { String str = strIn.trim(); StringBuilder result = new StringBuilder(str.length()); boolean wasWhitespace = false; for (int i = 0, j = 0; i < str.length(); i++) { char c = str.charAt(i); if (Character.isLetterOrDigit(c)) { if (i == 0) { c = Character.toUpperCase(c); } if (wasWhitespace) { c = Character.toUpperCase(c); wasWhitespace = false; } result.insert(j, c); j++; continue; } wasWhitespace = true; } return result.toString(); } /** * given a string, return the int of it; any parse error results in 0 being * returned * @param s the String to convert * @return int the String converted to an int type */ public static int smartStringToInt(String s) { return smartStringToInt(s, 0); } /** * given a string, return the int of it; any parse error results in default * being returned * @param s the String to convert * @param defaultValue the default value to assign to the return value if * the String didn't parse correctly * @return int the String converted to an int type */ public static int smartStringToInt(String s, int defaultValue) { int ret; try { ret = Integer.parseInt(s); } catch (NumberFormatException nfe) { ret = defaultValue; } return ret; } /** * given a string and a map, all instances of {key} will be replaced with * the value of 'key' in the map. so for example, "this is a {noun}" with a * Map that has ("noun" => "fish") would produce "this is a fish" * @param source the source string to be replaced * @param params the parameters to fill out the source string with * @return String Replaced String */ public static String replaceTags(String source, Map<String, String> params) { String ret = source; Iterator<Map.Entry<String, String>> i = params.entrySet().iterator(); while (i.hasNext()) { Map.Entry<String, String> me = i.next(); ret = StringUtils.replace(ret, "<" + me.getKey() + " />", me.getValue().toString()); } return ret; } /** * Using {@link java.util.StringTokenizer}, stringToList will convert the * string to a list of individual strings based on the standard default * StringTokenizer behavior. {@link java.util.StringTokenizer} <BR> * For example:<BR> * <BR> * "AAA BBB CCC DDD EEE FFF" <BR> * would be converted to a list containing:<BR> * {"AAA", "BBB", "CCC", "DDD", "EEE", "FFF"}<BR> * <BR> * * @param convertIn convert this string to a list * @return List version of specified string * @todo This should be removed and replaced with a call like: * Arrays.toList(str.split(","), String[]); */ public static List<String> stringToList(String convertIn) { StringTokenizer st = new StringTokenizer(convertIn); List<String> retval = new LinkedList<String>(); while (st.hasMoreTokens()) { retval.add(st.nextToken()); } return retval; } /** * Create a random password of the specified length * @param lengthIn length of random password * @return String new random password */ public static String makeRandomPassword(int lengthIn) { if (lengthIn < 5) { throw new IllegalArgumentException("Length too short"); } StringBuilder sb = new StringBuilder(lengthIn); Random rand = new Random(); int length = lengthIn; while (length-- > 5) { sb.append((char) ('A' + rand.nextInt(26))); } while (length-- >= 0) { sb.append((char) ('0' + rand.nextInt(10))); } return sb.toString(); } /** * Convert a class's name into the name without the package For example * StringUtils.getClassNameNoPackage(StringUtils.class) outputs: StringUtils * @param clazz The Class we want to get the name of * @return String name of the class without the package */ public static String getClassNameNoPackage(final Class clazz) { String fullyQualifiedClassName = clazz.getName(); int idx = fullyQualifiedClassName.lastIndexOf('.'); return fullyQualifiedClassName.substring(idx + 1); } /** * Returns a String for html parsing escapes html converts \n to a break tag * (<BR/<) converts urls beginning with http:// and https:// to links * Example: given http://foo.bar/example return <a * href="http://foo.bar/example">http://foo.bar/example</a> * @param convertIn the String we want to convert * @return html version of the String * @see org.apache.commons.lang.StringEscapeUtils */ public static String htmlifyText(String convertIn) { if (convertIn == null) { return null; } if (logger.isDebugEnabled()) { logger.debug("htmlifyText() - " + convertIn); } String retval = StringEscapeUtils.escapeHtml(convertIn); retval = retval.replaceAll("\\\\r\\\\n", "<br/>"); retval = retval.replaceAll("\\\\n", "<br/>"); retval = retval.replaceAll("\r\n", "<br/>"); retval = retval.replaceAll("\n", "<br/>"); Pattern startUrl = Pattern.compile("https?://"); // http:// or https:// Matcher next = startUrl.matcher(retval); boolean done = false; int previous = 0; // the starting index of the previously found url List<String> pieces = new LinkedList<String>(); /* * Separates the string into a list. Break points for different tokens * in the list are the start of each hyperlink (http:// or https://). * Basically, this finds the start of each piece we need to modify */ while (!done) { if (next.find()) { pieces.add(retval.substring(previous, next.start())); previous = next.start(); } else { pieces.add(retval.substring(previous)); done = true; } } /* * Adds each piece of the list back to the string modifying each that * starts with http:// or https:// This part finds the end of each url * and executes modifications */ Iterator<String> itr = pieces.iterator(); StringBuilder result = new StringBuilder(); while (itr.hasNext()) { String current = itr.next(); Matcher match = startUrl.matcher(current); if (match.find()) { // if this is a url int end = findEndOfUrl(current); StringBuilder modify = new StringBuilder("<a href=\""); if (end != -1) { // if the end of the url is not the end of the // token modify.append(current.substring(0, end).replaceAll("&", "&")); modify.append("\">"); modify.append(current.substring(0, end)); modify.append("</a>"); modify.append(current.substring(end)); } else { // if the end of the url is the end of the token modify.append(current.substring(0).replaceAll("&", "&")); modify.append("\">"); modify.append(current.substring(0)); modify.append("</a>"); } current = modify.toString(); } result.append(current); } if (logger.isDebugEnabled()) { logger.debug("htmlifyText() - returning: " + result); } return result.toString(); } /** * Join the strings contained in inputList with the separator. Returns null * if the input list is empty * * @param separator The String to glue the strings in inputList together * with * @param inputList The List of Strings to join * @return The joined String */ public static String join(String separator, Collection<String> inputList) { Iterator<String> itty = inputList.iterator(); return join(separator, itty); } /** * Join the strings contained in an iterator with a separator. Used by * join(String,List) and acts as a convenience for the few times we want to * use join without a list. * @param separator The String separator, use * <code>Localization.getInstance().getMessage("list delimiter")</code> for * the appropriate display separator. * @param itty The iterator containing display items. * @return The joined String */ public static String join(String separator, Iterator<String> itty) { if (!itty.hasNext()) { return null; } StringBuilder ret = new StringBuilder(); ret.append(itty.next()); while (itty.hasNext()) { ret.append(separator); ret.append(itty.next()); } return ret.toString(); } /** * Finds end of URL. * @param entireToken input String (URL) * @return position of last char of URL, returns -1 when entire String is URL */ private static int findEndOfUrl(String entireToken) { int space = entireToken.indexOf(' '); int line = entireToken.indexOf("<br/>"); int tag = entireToken.indexOf("<"); int end = -1; // end characters Set<Character> endChars = new HashSet<Character>(); endChars.add(new Character('.')); endChars.add(new Character(',')); if (space == -1 || (space > line && line != -1)) { end = line; } else { end = space; } if (end == -1 || (end > tag && tag != -1)) { end = tag; } // dot before the end if (end > 0 && (endChars.contains(new Character(entireToken.charAt(end - 1))))) { end--; } // dot at the end else if (endChars.contains(new Character(entireToken.charAt(entireToken.length() - 1)))) { end = entireToken.length() - 1; } return end; } /** * Converts the number of bytes to the appropriate unit (B, KB, or MB) * depending on how many bytes there actually are. It then localizes the * display of this number and formats the display with the units. It will * return fractional units for larger numbers (123.45 Mb and 123.4 Kb) * @param bytes the number of bytes used by a file * @return A localized and formatted string displaying the file size */ public static String displayFileSize(long bytes) { return displayFileSize(bytes, false); } /** * Converts the number of bytes to the appropriate unit (B, KB, or MB) * depending on how many bytes there actually are. It then localizes the * display of this number and formats the display with the units. It will * return fractional units for larger numbers (123.45 Mb and 123.4 Kb) * unless wholeNum == true. * @param bytes the number of bytes used by a file * @param wholeNum should the result be returned as a whole number? * @return A localized and formatted string displaying the file size */ public static String displayFileSize(long bytes, boolean wholeNum) { LocalizationService ls = LocalizationService.getInstance(); String number = null; String type = null; if (bytes >= (1024 * 1024)) { // show in megabytes (with two decimals) number = ls.formatNumber(new Double(bytes / (1024.0 * 1024)), (wholeNum ? 0 : 2)); type = "mb"; } else if (bytes >= 1024) { // show in kilobytes (with one decimal) number = ls.formatNumber(new Double(bytes / 1024.0), (wholeNum ? 0 : 1)); type = "kb"; } else { // show in bytes (with no decimals) number = ls.formatNumber(new Long(bytes), 0); type = "b"; } /* * the number that has now been localized is being used as an argument * for another localization call. I'm not really sure if the format of * displaying file sizes is truly something that needs to be localized. */ return ls.getMessage("file_size." + type, number); } private static void checkUnits(int maxUnit, int minUnit) { if (maxUnit < 0 || maxUnit > YEARS_UNITS) { throw new IllegalArgumentException("maxUnit out of range"); } if (minUnit < 0 || minUnit > YEARS_UNITS) { throw new IllegalArgumentException("minUnit out of range"); } if (maxUnit < minUnit) { throw new IllegalArgumentException("maxUnit must be >= minUnit!"); } } /** * Takes a target time (in msecs) and a maximum and minimum * time-unit-of-interest and returns an I18N string in * "x [maxUnits] y [units] ... z [minUnits] [ago:from now]" format. * * @param target timestamp in msecs-since-epoch of the event * @param maxUnit constant representing the maximum unit you want to * display. * @param minUnit constant representing the minimum unit you want to * display. * @return I18N string in "x [maxUnits] y [units] ... z [minUnits] [ago:from * now] format * @throws IllegalArgumentException if maxUnit or minUnit not recognized, or * if maxUnit < minUnit */ public static String categorizeTime(long target, int maxUnit, int minUnit) { checkUnits(maxUnit, minUnit); long now = System.currentTimeMillis(); long elapsedTime = (now > target ? (now - target) : (target - now)); // Start by filling an array with the number of "whole units" // for each of the units requested, from max to min. // NOTE: this whole process works only because the MILLIS_PER_UNIT // and UNITS_PER_NEXT arrays are in synch wit hthe _UNIT specifiers. long[] unitValues = new long[MILLIS_PER_UNIT.length]; for (int currUnit = maxUnit; currUnit >= minUnit; currUnit--) { if (currUnit == maxUnit) { unitValues[currUnit] = elapsedTime / MILLIS_PER_UNIT[currUnit]; } else { elapsedTime -= (unitValues[currUnit + 1] * MILLIS_PER_UNIT[currUnit + 1]); unitValues[currUnit] = elapsedTime / MILLIS_PER_UNIT[currUnit]; } } // Now, localize the unit-strings for each unit requested StringBuilder buff = new StringBuilder(); for (int currUnit = maxUnit; currUnit >= minUnit; currUnit--) { buff = buff.append(localizeUnit(unitValues[currUnit], currUnit)).append(" "); } // Now, localize the whole message and return it LocalizationService ls = LocalizationService.getInstance(); return ls.getMessage("timetag.categorizeLongFormat", buff.toString().trim(), getTense(now, target)); } // Return the "n <unit(s)>" string for the specified unit and n private static String localizeUnit(long interval, int unit) { LocalizationService ls = LocalizationService.getInstance(); String unitString = getUnitString(unit, interval); return ls.getMessage("timetag.oneunit", interval, unitString); } /** * Takes a target time (in msecs) and a time-unit-of-interest and returns an * I18N string in "x {units} {ago:from now}" format. * * @param target timestamp in msecs-since-epoch of the event * @param maxUnit constant representing the unit you want to display. The * code will "back off" from that to show the largest non-zero unit needed * to represent the time-difference * @return I18N string in "x {units} {ago:from now}" format * @throws IllegalArgumentException if maxUnit not recognized */ public static String categorizeTime(long target, int maxUnit) { checkUnits(maxUnit, maxUnit); long remainder; long valInMaxUnits; long now = System.currentTimeMillis(); long elapsedTime = (now > target ? (now - target) : (target - now)); int theUnit = findMaximumUnitFor(elapsedTime, maxUnit); // maxUnit is now one of entered-value, // largest-unit-smaller-than-elapsed, // or SECONDS_UNITS. Figure out how-many units we have, and what the // remainder is valInMaxUnits = elapsedTime / MILLIS_PER_UNIT[theUnit]; remainder = elapsedTime % MILLIS_PER_UNIT[theUnit]; // If the remainder > half a unit, round up. // Note: we don't round seconds if (theUnit != SECONDS_UNITS && remainder >= (MILLIS_PER_UNIT[theUnit - 1] * (UNITS_PER_NEXT[theUnit - 1] / 2))) { valInMaxUnits++; } // Now we have all the pieces - return the localized string LocalizationService ls = LocalizationService.getInstance(); String unitString = getUnitString(theUnit, valInMaxUnits); String tense = getTense(now, target); return ls.getMessage("timetag.categorizeFormat", valInMaxUnits, unitString, tense); } // What's the biggest unit <= specified elapsed time? private static int findMaximumUnitFor(long elapsed, int specifiedMax) { // Find max units < elapsed time int theUnit = specifiedMax; while (elapsed < MILLIS_PER_UNIT[theUnit] && theUnit > SECONDS_UNITS) { theUnit--; } return theUnit; } // What's the i18n string for the specified unit. // Handles 1-vs-many private static String getUnitString(int unit, long val) { LocalizationService ls = LocalizationService.getInstance(); switch (unit) { case SECONDS_UNITS: if (val == 1) { return ls.getMessage("timetag.second"); } return ls.getMessage("timetag.seconds"); case MINUTES_UNITS: if (val == 1) { return ls.getMessage("timetag.minute"); } return ls.getMessage("timetag.minutes"); case HOURS_UNITS: if (val == 1) { return ls.getMessage("timetag.hour"); } return ls.getMessage("timetag.hours"); case DAYS_UNITS: if (val == 1) { return ls.getMessage("timetag.day"); } return ls.getMessage("timetag.days"); case WEEKS_UNITS: if (val == 1) { return ls.getMessage("timetag.week"); } return ls.getMessage("timetag.weeks"); case MONTHS_UNITS: if (val == 1) { return ls.getMessage("timetag.month"); } return ls.getMessage("timetag.months"); case YEARS_UNITS: if (val == 1) { return ls.getMessage("timetag.year"); } return ls.getMessage("timetag.years"); default: return ls.getMessage("timetag.unknown"); } } // Is Target in the past or the future? private static String getTense(long now, long target) { LocalizationService ls = LocalizationService.getInstance(); if (now < target) { return ls.getMessage("timetag.futuretense"); } return ls.getMessage("timetag.pasttense"); } /** * Convert an incoming web-string (with \r\n EOL) to a Linux string (with \n * as EOL) * @param inWebStr string from a web form * @return Linux-EOL'd-string TODO: This shoudl be generalized to handle * other kinds of non-Linux EOLs, so we can use it on uploaded files as well */ public static String webToLinux(String inWebStr) { return (inWebStr == null ? null : inWebStr.replaceAll("\r\n", "\n")); } /** * Encode a string for use in a URL. * @param source Source string to encode. * @return Encoded version of source. */ public static String urlEncode(String source) { String encodedParam = null; try { encodedParam = URLEncoder.encode(source, "UTF-8"); } catch (Exception e) { encodedParam = URLEncoder.encode(source); } return encodedParam; } /** * Basically turns an html or xml snippet to plain text Meant to be used * along with string resources xml file messages. This is a best guess plain * text conversion.. For example the following text * * <pre> * <p>You donot have enough entitlements for <strong> * xyz system </strong></p> * will get converted to * You donot have enough entitlements for xyz system * It just returns the original snippet back in the case * of an xml error (however it throws a warning)... * @param html the html/xml String resources snippet to convert * @param html html input * @return the plain text version. */ public static String toPlainText(String html) { XmlToPlainText helper = new XmlToPlainText(); return helper.convert(html); } /** * Converts an object to json representation * @param obj any object * @return the jsoned representation */ public static String toJson(Object obj) { JSONWriter writer = new JSONWriter(); return writer.write(obj); } /** * Converts a jsoned representation back to the object * @param json json string * @return the converted object.. The caller is expected to know the kind of * returned object. */ public static Object jsonToObject(String json) { JSONReader reader = new JSONReader(); return reader.read(json); } /** * Convert a string of options (name value pairs separated by '=', where the * pairs are seperated by 'separator'), into a map. * @param options the string of options * @param errorKey the localization key of the error message to throw if we * can't parse it correctly * @param separator the separator the separates different name value pairs * @return a map containing name value pairs of options * @throws ValidatorException if there isn't an '=' sign seperating the * pairs */ public static Map<String, String> convertOptionsToMap(String options, String errorKey, String separator) throws ValidatorException { Map<String, String> toReturn = new HashMap<String, String>(); StringTokenizer token = new StringTokenizer(options, separator); while (token.hasMoreElements()) { String option = token.nextToken(); if (!StringUtils.isBlank(option)) { //Skip blank lines String[] args = option.split("=", 2); if (args.length != 2) { ValidatorException.raiseException(errorKey, option); } else { toReturn.put(args[0], args[1].trim()); } } } return toReturn; } /** * Convert a map of kernel options into a string: name1=value1 name2=value2 * name3=value * @param map the map of options * @param seperator the seperator to seperate the pairs with * @return the formatted string */ public static String convertMapToString(Map<String, Object> map, String seperator) { StringBuilder string = new StringBuilder(); for (Object key : map.keySet()) { string.append(key + "=" + map.get(key) + seperator); } return string.toString(); } /** * Add a path onto another path (either local path or networked service). * This ensures double '/'s aren't included * @param originalPath The start of the path (i.e. http://localhost/, or * /var/www/) * @param toAdd what to add (i.e. /pub/test.file) * @return the full path with no duplicate '/'s */ public static String addPath(String originalPath, String toAdd) { return FilenameUtils.normalize(originalPath + File.separator + toAdd); } /** * * @param b The byteArray of the compressedDigest stream * @return the hexString equivalent */ public static String getHexString(byte[] b) { String result = ""; for (int i = 0; i < b.length; i++) { result += Integer.toString((b[i] & 0xff) + 0x100, 16).substring(1); } return result; } /** * Checks, whether string contains invalid xml chars * @param string to check * @return true if there're xml invalid chars */ public static Boolean containsInvalidXmlChars2(String string) { for (int i = 0; i < string.length(); i++) { if (!XMLChar.isValid(string.charAt(i))) { return Boolean.TRUE; } } return Boolean.FALSE; } /** * Tests String byte-length and eventually truncates * @param str input String * @param length target length * @return truncated String */ public static String getBytesTruncatedString(String str, int length) { try { byte[] bytes = str.getBytes("UTF-8"); if (bytes.length > length) { return new String(Arrays.copyOf(bytes, length), "UTF-8"); } } catch (UnsupportedEncodingException e) { logger.warn("Unable to convert to UTF-8 bytes."); } return str; } /** * Returns null, if string is empty, is already null or contains tabs, whitespace, * line breaks or other non-valuable content. It also matches Unicode characters. * @param str string * @return null if empty (see description), original string otherwise. */ public static String nullOrValue(String str) { return (str == null ? "" : str).trim().replaceAll("[^\\p{L}\\p{Nd}]", "").isEmpty() ? null : str; } /** * Returns null, if string is empty * @param str string * @return null if empty, original string otherwise */ public static String nullIfEmpty(String str) { return StringUtils.isBlank(str) ? null : str; } /** * Check if the script seems to be valid. * @param script Script body * @return null if no errors, otherwise result enum with the error code. */ public static ScriptCheckResult scriptPrematureCheck(String script) { ScriptCheckResult result = null; // Assume the script is valid. if (StringUtil.nullOrValue(script) == null) { result = ScriptCheckResult.NO_SCRIPT_DEFINED; } else { boolean hasShellDeclaration = false; int codeLines = 0; String[] lines = script.split(System.getProperty("line.separator")); for (int i = 0; i < lines.length; i++) { String line = StringUtil.nullOrValue(lines[i]); if (line != null) { line = line.trim(); if (line.startsWith("#!/") && !hasShellDeclaration) { hasShellDeclaration = true; } else if (!line.startsWith("#")) { codeLines++; } } } if (codeLines == 0) { result = ScriptCheckResult.NO_SCRIPT_DEFINED; } else if (!hasShellDeclaration) { result = ScriptCheckResult.NO_SHELL_DEFINED; } } return result; } }