diff --git a/build-logic/docs-core/src/main/groovy/grails/doc/internal/StringEscapeCategory.java b/build-logic/docs-core/src/main/groovy/grails/doc/internal/StringEscapeCategory.groovy similarity index 58% rename from build-logic/docs-core/src/main/groovy/grails/doc/internal/StringEscapeCategory.java rename to build-logic/docs-core/src/main/groovy/grails/doc/internal/StringEscapeCategory.groovy index a863a760d1e..cda703f5230 100644 --- a/build-logic/docs-core/src/main/groovy/grails/doc/internal/StringEscapeCategory.java +++ b/build-logic/docs-core/src/main/groovy/grails/doc/internal/StringEscapeCategory.groovy @@ -17,38 +17,39 @@ * under the License. */ -package grails.doc.internal; +package grails.doc.internal -import java.net.URI; -import java.net.URISyntaxException; +import groovy.transform.CompileStatic +import org.apache.commons.text.StringEscapeUtils -import org.apache.commons.text.StringEscapeUtils; +@CompileStatic +class StringEscapeCategory { -public class StringEscapeCategory { private StringEscapeCategory() { } - public static String encodeAsUrlPath(String str) { + static String encodeAsUrlPath(String str) { try { - String uri = new URI("http", "localhost", '/' + str, "").toASCIIString(); - return uri.substring(17, uri.length() - 1); + String uri = new URI('http', 'localhost', '/' + str, '').toASCIIString() + return uri.substring(17, uri.length() - 1) } catch (URISyntaxException ex) { - throw new RuntimeException(ex); + throw new RuntimeException(ex) } } - public static String encodeAsUrlFragment(String str) { + static String encodeAsUrlFragment(String str) { try { - String uri = new URI("http", "localhost", "/", str).toASCIIString(); - return uri.substring(18, uri.length()); + String uri = new URI('http', 'localhost', '/', str).toASCIIString() + return uri.substring(18, uri.length()) } catch (URISyntaxException ex) { - throw new RuntimeException(ex); + throw new RuntimeException(ex) } } - public static String encodeAsHtml(String str) { - return StringEscapeUtils.escapeHtml4(str); + static String encodeAsHtml(String str) { + return StringEscapeUtils.escapeHtml4(str) } + } diff --git a/build-logic/docs-core/src/test/groovy/grails/doc/internal/StringEscapeCategorySpec.groovy b/build-logic/docs-core/src/test/groovy/grails/doc/internal/StringEscapeCategorySpec.groovy new file mode 100644 index 00000000000..6df68c63776 --- /dev/null +++ b/build-logic/docs-core/src/test/groovy/grails/doc/internal/StringEscapeCategorySpec.groovy @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package grails.doc.internal + +import spock.lang.Specification + +class StringEscapeCategorySpec extends Specification { + + void 'empty and reserved characters are encoded consistently'() { + expect: + StringEscapeCategory.encodeAsUrlPath('') == '' + StringEscapeCategory.encodeAsUrlPath('a/b?c#d') == 'a/b%3Fc%23d' + StringEscapeCategory.encodeAsUrlFragment('') == '' + StringEscapeCategory.encodeAsUrlFragment('a/b?c#d') == 'a/b?c%23d' + StringEscapeCategory.encodeAsHtml('') == '' + StringEscapeCategory.encodeAsHtml('"quoted" & \'single\'') == '"quoted" & \'single\'' + StringEscapeCategory.encodeAsHtml(null) == null + } + + void 'the category is a static utility'() { + expect: + StringEscapeCategory.declaredConstructors.every { java.lang.reflect.Modifier.isPrivate(it.modifiers) } + } + +} diff --git a/grails-gradle/common/src/main/groovy/org/apache/grails/gradle/common/XmlParserFeature.java b/grails-gradle/common/src/main/groovy/org/apache/grails/gradle/common/XmlParserFeature.groovy similarity index 82% rename from grails-gradle/common/src/main/groovy/org/apache/grails/gradle/common/XmlParserFeature.java rename to grails-gradle/common/src/main/groovy/org/apache/grails/gradle/common/XmlParserFeature.groovy index fb10d909fb0..ad8377eadf2 100644 --- a/grails-gradle/common/src/main/groovy/org/apache/grails/gradle/common/XmlParserFeature.java +++ b/grails-gradle/common/src/main/groovy/org/apache/grails/gradle/common/XmlParserFeature.groovy @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.grails.gradle.common; +package org.apache.grails.gradle.common /** * Registered SAX and Xerces parser feature identifiers used to harden XML parsing. @@ -36,7 +36,7 @@ * * @since 8.0.0 */ -public enum XmlParserFeature { +enum XmlParserFeature { /** * Rejects any document carrying a {@code DOCTYPE} declaration. @@ -47,22 +47,22 @@ public enum XmlParserFeature { * {@code web.xml} — routinely carry a DOCTYPE, so their readers need a parser that leaves this * disabled while keeping the entity and DTD features below switched off. */ - DISALLOW_DOCTYPE_DECL("http://apache.org/xml/features/disallow-doctype-decl"), + DISALLOW_DOCTYPE_DECL('http://apache.org/xml/features/disallow-doctype-decl'), /** * Blocks resolution of external general entities, the primary XXE vector. */ - EXTERNAL_GENERAL_ENTITIES("http://xml.org/sax/features/external-general-entities"), + EXTERNAL_GENERAL_ENTITIES('http://xml.org/sax/features/external-general-entities'), /** * Blocks resolution of external parameter entities. */ - EXTERNAL_PARAMETER_ENTITIES("http://xml.org/sax/features/external-parameter-entities"), + EXTERNAL_PARAMETER_ENTITIES('http://xml.org/sax/features/external-parameter-entities'), /** * Stops the parser building a grammar from a DTD. */ - LOAD_DTD_GRAMMAR("http://apache.org/xml/features/nonvalidating/load-dtd-grammar"), + LOAD_DTD_GRAMMAR('http://apache.org/xml/features/nonvalidating/load-dtd-grammar'), /** * Skips external DTD subsets instead of retrieving them. @@ -71,24 +71,24 @@ public enum XmlParserFeature { * raises an error when a document references an external DTD. Skipping is what allows a * descriptor that names a DTD, such as a JSP 1.2 tag library, to parse without retrieving it. */ - LOAD_EXTERNAL_DTD("http://apache.org/xml/features/nonvalidating/load-external-dtd"); + LOAD_EXTERNAL_DTD('http://apache.org/xml/features/nonvalidating/load-external-dtd') - private final String featureName; + private final String featureName XmlParserFeature(String featureName) { - this.featureName = featureName; + this.featureName = featureName } /** * @return the registered identifier to pass to {@code setFeature} */ - public String getFeatureName() { - return featureName; + String getFeatureName() { + return featureName } @Override - public String toString() { - return featureName; + String toString() { + return featureName } } diff --git a/grails-gradle/model/src/main/groovy/grails/util/CollectionUtils.java b/grails-gradle/model/src/main/groovy/grails/util/CollectionUtils.groovy similarity index 59% rename from grails-gradle/model/src/main/groovy/grails/util/CollectionUtils.java rename to grails-gradle/model/src/main/groovy/grails/util/CollectionUtils.groovy index 7834bc2ec50..b4e20eaa400 100644 --- a/grails-gradle/model/src/main/groovy/grails/util/CollectionUtils.java +++ b/grails-gradle/model/src/main/groovy/grails/util/CollectionUtils.groovy @@ -16,59 +16,52 @@ * specific language governing permissions and limitations * under the License. */ -package grails.util; +package grails.util -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import groovy.transform.CompileStatic /** * Collection utility methods. * * @author Burt Beckwith */ -public class CollectionUtils { +@CompileStatic +class CollectionUtils { private CollectionUtils() { // static only } - @SuppressWarnings({ "rawtypes", "unchecked" }) - public static Map newMap(Object... keysAndValues) { + @SuppressWarnings([ 'rawtypes', 'unchecked' ]) + static Map newMap(Object... keysAndValues) { if (keysAndValues == null) { - return Collections.emptyMap(); + return Collections.emptyMap() } if (keysAndValues.length % 2 == 1) { - throw new IllegalArgumentException("Must have an even number of keys and values"); + throw new IllegalArgumentException('Must have an even number of keys and values') } - Map map = new HashMap<>(); + Map map = new HashMap<>() for (int i = 0; i < keysAndValues.length; i += 2) { - map.put((K) keysAndValues[i], (V) keysAndValues[i + 1]); + map.put((K) keysAndValues[i], (V) keysAndValues[i + 1]) } - return map; + return map } - public static Set newSet(T... values) { + static Set newSet(T... values) { if (values == null) { - return Collections.emptySet(); + return Collections.emptySet() } - return new HashSet<>(Arrays.asList(values)); + return new HashSet<>(Arrays.asList(values)) } - public static List newList(T... values) { + static List newList(T... values) { if (values == null) { - return Collections.emptyList(); + return Collections.emptyList() } - return new ArrayList<>(Arrays.asList(values)); + return new ArrayList<>(Arrays.asList(values)) } /** @@ -78,11 +71,12 @@ public static List newList(T... values) { * @param key The key that holds the child map * @return The child map */ - public static Map getOrCreateChildMap(Map parent, String key) { - Object o = parent.get(key); + static Map getOrCreateChildMap(Map parent, String key) { + Object o = parent[key] if (o instanceof Map) { - return (Map) o; + return o } - return new LinkedHashMap(); + return [:] } + } diff --git a/grails-gradle/model/src/main/groovy/grails/util/GrailsNameUtils.java b/grails-gradle/model/src/main/groovy/grails/util/GrailsNameUtils.groovy similarity index 64% rename from grails-gradle/model/src/main/groovy/grails/util/GrailsNameUtils.java rename to grails-gradle/model/src/main/groovy/grails/util/GrailsNameUtils.groovy index 2fe96736271..74ace013354 100644 --- a/grails-gradle/model/src/main/groovy/grails/util/GrailsNameUtils.java +++ b/grails-gradle/model/src/main/groovy/grails/util/GrailsNameUtils.groovy @@ -16,25 +16,21 @@ * specific language governing permissions and limitations * under the License. */ -package grails.util; +package grails.util -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; +import groovy.transform.CompileStatic +import groovy.transform.PackageScope /** * Utility methods for converting between different name types, * for example from class names -> property names and vice-versa. The * key aspect of this class is that it has no dependencies outside the JDK! */ -public class GrailsNameUtils { +@CompileStatic +class GrailsNameUtils { - private static final String PROPERTY_SET_PREFIX = "set"; - private static final String PROPERTY_GET_PREFIX = "get"; + private static final String PROPERTY_SET_PREFIX = 'set' + private static final String PROPERTY_GET_PREFIX = 'get' private GrailsNameUtils() { } @@ -44,9 +40,9 @@ private GrailsNameUtils() { * @param propertyName The property name * @return The setter equivalent */ - public static String getSetterName(String propertyName) { - final String suffix = getSuffixForGetterOrSetter(propertyName); - return PROPERTY_SET_PREFIX + suffix; + static String getSetterName(String propertyName) { + final String suffix = getSuffixForGetterOrSetter(propertyName) + return PROPERTY_SET_PREFIX + suffix } /** @@ -54,21 +50,21 @@ public static String getSetterName(String propertyName) { * @param propertyName * @return The name for the getter method for this property, if it were to exist, i.e. getConstraints */ - public static String getGetterName(String propertyName) { - final String suffix = getSuffixForGetterOrSetter(propertyName); - return PROPERTY_GET_PREFIX + suffix; + static String getGetterName(String propertyName) { + final String suffix = getSuffixForGetterOrSetter(propertyName) + return PROPERTY_GET_PREFIX + suffix } private static String getSuffixForGetterOrSetter(String propertyName) { - final String suffix; + final String suffix if (propertyName.length() > 1 && Character.isLowerCase(propertyName.charAt(0)) && Character.isUpperCase(propertyName.charAt(1))) { - suffix = propertyName; + suffix = propertyName } else { - suffix = Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1); + suffix = String.valueOf(Character.toUpperCase(propertyName.charAt(0))) + propertyName.substring(1) } - return suffix; + return suffix } /** @@ -78,16 +74,16 @@ private static String getSuffixForGetterOrSetter(String propertyName) { * @param trailingName The trailing name * @return The class name */ - public static String getClassName(String logicalName, String trailingName) { + static String getClassName(String logicalName, String trailingName) { if (isBlank(logicalName)) { - throw new IllegalArgumentException("Argument [logicalName] cannot be null or blank"); + throw new IllegalArgumentException('Argument [logicalName] cannot be null or blank') } - String className = logicalName.substring(0, 1).toUpperCase(Locale.ENGLISH) + logicalName.substring(1); + String className = logicalName.substring(0, 1).toUpperCase(Locale.ENGLISH) + logicalName.substring(1) if (trailingName != null) { - className = className + trailingName; + className = className + trailingName } - return className; + return className } /** @@ -95,10 +91,10 @@ public static String getClassName(String logicalName, String trailingName) { * * @param cls The class name */ - public static String getFullClassName(Class cls) { - String className = cls.getName(); + static String getFullClassName(Class cls) { + String className = cls.name - return getFullClassName(className); + return getFullClassName(className) } /** @@ -106,12 +102,12 @@ public static String getFullClassName(Class cls) { * * @param className The class name */ - public static String getFullClassName(String className) { - final int i = className.indexOf('$'); + static String getFullClassName(String className) { + final int i = className.indexOf('$') if (i > -1) { - className = className.substring(0, i); + className = className.substring(0, i) } - return className; + return className } /** @@ -120,8 +116,8 @@ public static String getFullClassName(String className) { * @param logicalName The logical name * @return The class name */ - public static String getClassName(String logicalName) { - return getClassName(logicalName, ""); + static String getClassName(String logicalName) { + return getClassName(logicalName, '') } /** @@ -130,25 +126,25 @@ public static String getClassName(String logicalName) { * @param name The name to convert * @return The property name representation */ - public static String getClassNameRepresentation(String name) { - if (name == null || name.length() == 0) { - return ""; + static String getClassNameRepresentation(String name) { + if (name == null || name.isEmpty()) { + return '' } - StringBuilder buf = new StringBuilder(); - String[] tokens = name.split("[^\\w\\d]"); - for (String token1 : tokens) { - String token = token1.trim(); - int length = token.length(); + StringBuilder buf = new StringBuilder() + String[] tokens = name.split('[^\\w\\d]') + for (String token1 in tokens) { + String token = token1.trim() + int length = token.length() if (length > 0) { - buf.append(token.substring(0, 1).toUpperCase(Locale.ENGLISH)); + buf << token.substring(0, 1).toUpperCase(Locale.ENGLISH) if (length > 1) { - buf.append(token.substring(1)); + buf << token.substring(1) } } } - return buf.toString(); + return buf.toString() } /** @@ -159,20 +155,19 @@ public static String getClassNameRepresentation(String name) { */ private static String getClassNameForLowerCaseHyphenSeparatedName(String name) { // Handle null and empty strings. - if (isBlank(name)) return name; + if (isBlank(name)) return name if (name.indexOf('-') == -1) { - return name.substring(0, 1).toUpperCase() + name.substring(1); + return name.substring(0, 1).toUpperCase() + name.substring(1) } - StringBuilder buf = new StringBuilder(); - String[] tokens = name.split("-"); - for (String token : tokens) { - if (token == null || token.length() == 0) continue; - buf.append(token.substring(0, 1).toUpperCase()) - .append(token.substring(1)); + StringBuilder buf = new StringBuilder() + String[] tokens = name.split('-') + for (String token in tokens) { + if (token == null || token.isEmpty()) continue + buf << token.substring(0, 1).toUpperCase() << token.substring(1) } - return buf.toString(); + return buf.toString() } /** @@ -183,8 +178,8 @@ private static String getClassNameForLowerCaseHyphenSeparatedName(String name) { * @param trailingName The trailing name such as "Controller" or "TagLib" * @return The logical class name */ - public static String getLogicalName(Class clazz, String trailingName) { - return getLogicalName(clazz.getName(), trailingName); + static String getLogicalName(Class clazz, String trailingName) { + return getLogicalName(clazz.name, trailingName) } /** @@ -193,26 +188,26 @@ public static String getLogicalName(Class clazz, String trailingName) { * @param trailingName The trailing name * @return The logical name */ - public static String getLogicalName(String name, String trailingName) { + static String getLogicalName(String name, String trailingName) { if (isBlank(trailingName)) { - return name; + return name } - String shortName = getShortName(name); + String shortName = getShortName(name) if (shortName.indexOf(trailingName) == -1) { - return name; + return name } - return shortName.substring(0, shortName.length() - trailingName.length()); + return shortName.substring(0, shortName.length() - trailingName.length()) } - public static String getLogicalPropertyName(String className, String trailingName) { + static String getLogicalPropertyName(String className, String trailingName) { if (!isBlank(className) && !isBlank(trailingName)) { if (className.length() == trailingName.length() + 1 && className.endsWith(trailingName)) { - return className.substring(0, 1).toLowerCase(); + return className.substring(0, 1).toLowerCase() } } - return getLogicalName(getPropertyName(className), trailingName); + return getLogicalName(getPropertyName(className), trailingName) } /** @@ -220,8 +215,8 @@ public static String getLogicalPropertyName(String className, String trailingNam * @param name The name to convert * @return The property name version */ - public static String getPropertyName(String name) { - return getPropertyNameRepresentation(name); + static String getPropertyName(String name) { + return getPropertyNameRepresentation(name) } /** @@ -229,8 +224,8 @@ public static String getPropertyName(String name) { * @param clazz The clazz to convert * @return The property name version */ - public static String getPropertyName(Class clazz) { - return getPropertyNameRepresentation(clazz); + static String getPropertyName(Class clazz) { + return getPropertyNameRepresentation(clazz) } /** @@ -239,8 +234,8 @@ public static String getPropertyName(Class clazz) { * @param targetClass The class to get the property name for * @return A property name reperesentation of the class name (eg. MyClass becomes myClass) */ - public static String getPropertyNameRepresentation(Class targetClass) { - return getPropertyNameRepresentation(getShortName(targetClass)); + static String getPropertyNameRepresentation(Class targetClass) { + return getPropertyNameRepresentation(getShortName(targetClass)) } /** @@ -249,28 +244,28 @@ public static String getPropertyNameRepresentation(Class targetClass) { * @param name The name to convert * @return The property name representation */ - public static String getPropertyNameRepresentation(String name) { + static String getPropertyNameRepresentation(String name) { // Strip any package from the name. - int pos = name.lastIndexOf('.'); + int pos = name.lastIndexOf('.') if (pos != -1) { - name = name.substring(pos + 1); + name = name.substring(pos + 1) } if (name.isEmpty()) { - return name; + return name } // Check whether the name begins with two upper case letters. if (name.length() > 1 && Character.isUpperCase(name.charAt(0)) && Character.isUpperCase(name.charAt(1))) { - return name; + return name } - String propertyName = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1); + String propertyName = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1) if (propertyName.indexOf(' ') > -1) { - propertyName = propertyName.replaceAll("\\s", ""); + propertyName = propertyName.replaceAll('\\s', '') } - return propertyName; + return propertyName } /** @@ -279,8 +274,8 @@ public static String getPropertyNameRepresentation(String name) { * @param name The lower case hyphen separated name * @return The property name equivalent */ - public static String getPropertyNameForLowerCaseHyphenSeparatedName(String name) { - return getPropertyName(getClassNameForLowerCaseHyphenSeparatedName(name)); + static String getPropertyNameForLowerCaseHyphenSeparatedName(String name) { + return getPropertyName(getClassNameForLowerCaseHyphenSeparatedName(name)) } /** @@ -289,8 +284,8 @@ public static String getPropertyNameForLowerCaseHyphenSeparatedName(String name) * @param targetClass The class to get a short name for * @return The short name of the class */ - public static String getShortName(Class targetClass) { - return getShortName(targetClass.getName()); + static String getShortName(Class targetClass) { + return getShortName(targetClass.name) } /** @@ -299,12 +294,12 @@ public static String getShortName(Class targetClass) { * @param className The class name to get a short name for * @return The short name of the class */ - public static String getShortName(String className) { - int i = className.lastIndexOf("."); + static String getShortName(String className) { + int i = className.lastIndexOf('.') if (i > -1) { - className = className.substring(i + 1, className.length()); + className = className.substring(i + 1, className.length()) } - return className; + return className } /** @@ -313,13 +308,13 @@ public static String getShortName(String className) { * @param className The class name to get the package prefix for * @return The package prefix of the class */ - public static String getPackageName(String className) { - int i = className.lastIndexOf("."); - String packageName = ""; + static String getPackageName(String className) { + int i = className.lastIndexOf('.') + String packageName = '' if (i > -1) { - packageName = className.substring(0, i); + packageName = className.substring(0, i) } - return packageName; + return packageName } /** @@ -329,8 +324,8 @@ public static String getPackageName(String className) { * @param clazz The class to convert * @return The script name representation */ - public static String getScriptName(Class clazz) { - return clazz == null ? null : getScriptName(clazz.getName()); + static String getScriptName(Class clazz) { + return clazz == null ? null : getScriptName(clazz.name) } /** @@ -340,15 +335,15 @@ public static String getScriptName(Class clazz) { * @param name The class name to convert. * @return The script name representation. */ - public static String getScriptName(String name) { + static String getScriptName(String name) { if (name == null) { - return null; + return null } - if (name.endsWith(".groovy")) { - name = name.substring(0, name.length() - 7); + if (name.endsWith('.groovy')) { + name = name.substring(0, name.length() - 7) } - return getNaturalName(name).replaceAll("\\s", "-").toLowerCase(); + return getNaturalName(name).replaceAll('\\s', '-').toLowerCase() } /** @@ -357,8 +352,8 @@ public static String getScriptName(String name) { * @param scriptName The script name * @return A class name */ - public static String getNameFromScript(String scriptName) { - return getClassNameForLowerCaseHyphenSeparatedName(scriptName); + static String getNameFromScript(String scriptName) { + return getClassNameForLowerCaseHyphenSeparatedName(scriptName) } /** @@ -372,16 +367,16 @@ public static String getNameFromScript(String scriptName) { * @throws IllegalArgumentException if the given descriptor name is * not valid, i.e. if it doesn't end with "GrailsPlugin.groovy". */ - public static String getPluginName(String descriptorName) { - if (descriptorName == null || descriptorName.length() == 0) { - return descriptorName; + static String getPluginName(String descriptorName) { + if (descriptorName == null || descriptorName.isEmpty()) { + return descriptorName } - if (!descriptorName.endsWith("GrailsPlugin.groovy")) { - throw new IllegalArgumentException("Plugin descriptor name is not valid: " + descriptorName); + if (!descriptorName.endsWith('GrailsPlugin.groovy')) { + throw new IllegalArgumentException('Plugin descriptor name is not valid: ' + descriptorName) } - return getScriptName(descriptorName.substring(0, descriptorName.indexOf("GrailsPlugin.groovy"))); + return getScriptName(descriptorName.substring(0, descriptorName.indexOf('GrailsPlugin.groovy'))) } /** @@ -389,52 +384,52 @@ public static String getPluginName(String descriptorName) { * @param name The property name to convert * @return The converted property name */ - public static String getNaturalName(String name) { - name = getShortName(name); - List words = new ArrayList<>(); - int i = 0; - char[] chars = name.toCharArray(); + static String getNaturalName(String name) { + name = getShortName(name) + List words = new ArrayList<>() + int i = 0 + char[] chars = name.toCharArray() for (int j = 0; j < chars.length; j++) { - char c = chars[j]; - String w; + char c = chars[j] + String w if (i >= words.size()) { - w = ""; - words.add(i, w); + w = '' + words.add(i, w) } else { - w = words.get(i); + w = words.get(i) } if (Character.isLowerCase(c) || Character.isDigit(c)) { - if (Character.isLowerCase(c) && w.length() == 0) { - c = Character.toUpperCase(c); + if (Character.isLowerCase(c) && w.isEmpty()) { + c = Character.toUpperCase(c) } else if (w.length() > 1 && Character.isUpperCase(w.charAt(w.length() - 1))) { - w = ""; - words.add(++i, w); + w = '' + words.add(++i, w) } - words.set(i, w + c); + words.set(i, w + c) } else if (Character.isUpperCase(c)) { - if ((i == 0 && w.length() == 0) || (Character.isUpperCase(w.charAt(w.length() - 1)) && Character.isUpperCase(chars[j - 1]))) { - words.set(i, w + c); + if ((i == 0 && w.isEmpty()) || (Character.isUpperCase(w.charAt(w.length() - 1)) && Character.isUpperCase(chars[j - 1]))) { + words.set(i, w + c) } else { - words.add(++i, String.valueOf(c)); + words.add(++i, String.valueOf(c)) } } } - StringBuilder buf = new StringBuilder(); + StringBuilder buf = new StringBuilder() for (Iterator j = words.iterator(); j.hasNext();) { - String word = j.next(); - buf.append(word); + String word = j.next() + buf << word if (j.hasNext()) { - buf.append(' '); + buf << ' ' } } - return buf.toString(); + return buf.toString() } /** @@ -449,8 +444,8 @@ else if (Character.isUpperCase(c)) { * @return true if the string is null, or * blank. */ - public static boolean isBlank(String str) { - return str == null || str.trim().length() == 0; + static boolean isBlank(String str) { + return str == null || str.trim().isEmpty() } /** @@ -458,10 +453,10 @@ public static boolean isBlank(String str) { * @param object The object * @return The property name convention */ - public static String getPropertyNameConvention(Object object) { - String suffix = ""; + static String getPropertyNameConvention(Object object) { + String suffix = '' - return getPropertyNameConvention(object, suffix); + return getPropertyNameConvention(object, suffix) } /** @@ -470,15 +465,15 @@ public static String getPropertyNameConvention(Object object) { * @param packageName The name of the package * @return True if it is valid */ - public static boolean isValidJavaPackage(String packageName) { - if (isBlank(packageName)) return false; - final String[] parts = packageName.split("\\."); - for (String part : parts) { + static boolean isValidJavaPackage(String packageName) { + if (isBlank(packageName)) return false + final String[] parts = packageName.split('\\.') + for (String part in parts) { if (!isValidJavaIdentifier(part)) { - return false; + return false } } - return true; + return true } /** @@ -487,21 +482,21 @@ public static boolean isValidJavaPackage(String packageName) { * @param name The name * @return True if it is */ - public static boolean isValidJavaIdentifier(String name) { - if (isBlank(name)) return false; + static boolean isValidJavaIdentifier(String name) { + if (isBlank(name)) return false - final char[] chars = name.toCharArray(); + final char[] chars = name.toCharArray() if (!Character.isJavaIdentifierStart(chars[0])) { - return false; + return false } - for (char c : chars) { + for (char c in chars) { if (!Character.isJavaIdentifierPart(c)) { - return false; + return false } } - return true; + return true } /** @@ -510,46 +505,46 @@ public static boolean isValidJavaIdentifier(String name) { * @param suffix The suffix to append to the name. * @return The property name convention */ - public static String getPropertyNameConvention(Object object, String suffix) { + static String getPropertyNameConvention(Object object, String suffix) { if (object != null) { - Class type = object.getClass(); - if (type.isArray()) { - return getPropertyName(type.getComponentType()) + suffix + "Array"; + Class type = object.getClass() + if (type.array) { + return getPropertyName(type.componentType) + suffix + 'Array' } if (object instanceof Collection) { - Collection coll = (Collection) object; + Collection coll = object if (coll.isEmpty()) { - return "emptyCollection"; + return 'emptyCollection' } - Object first = coll.iterator().next(); + Object first = coll.iterator().next() if (coll instanceof List) { - return getPropertyName(first.getClass()) + suffix + "List"; + return getPropertyName(first.getClass()) + suffix + 'List' } if (coll instanceof Set) { - return getPropertyName(first.getClass()) + suffix + "Set"; + return getPropertyName(first.getClass()) + suffix + 'Set' } - return getPropertyName(first.getClass()) + suffix + "Collection"; + return getPropertyName(first.getClass()) + suffix + 'Collection' } if (object instanceof Map) { - Map map = (Map) object; + Map map = object if (map.isEmpty()) { - return "emptyMap"; + return 'emptyMap' } - Object entry = map.values().iterator().next(); + Object entry = map.values().iterator().next() if (entry != null) { - return getPropertyName(entry.getClass()) + suffix + "Map"; + return getPropertyName(entry.getClass()) + suffix + 'Map' } } else { - return getPropertyName(object.getClass()) + suffix; + return getPropertyName(object.getClass()) + suffix } } - return null; + return null } /** @@ -559,8 +554,8 @@ public static String getPropertyNameConvention(Object object, String suffix) { * @param getterName The getter name * @return The property name equivalent */ - public static String getPropertyForGetter(String getterName) { - return getPropertyForGetter(getterName, boolean.class); + static String getPropertyForGetter(String getterName) { + return getPropertyForGetter(getterName, boolean.class) } /** @@ -571,8 +566,8 @@ public static String getPropertyForGetter(String getterName) { * @param returnType The type the method returns * @return The property name equivalent */ - public static String getPropertyForGetter(String getterName, Class returnType) { - return getPropertyForGetter(getterName, returnType.getName()); + static String getPropertyForGetter(String getterName, Class returnType) { + return getPropertyForGetter(getterName, returnType.name) } /** @@ -583,18 +578,18 @@ public static String getPropertyForGetter(String getterName, Class returnType) { * @param returnType The type the method returns * @return The property name equivalent */ - public static String getPropertyForGetter(String getterName, String returnType) { - if (getterName == null || getterName.length() == 0) return null; + static String getPropertyForGetter(String getterName, String returnType) { + if (getterName == null || getterName.isEmpty()) return null - if (getterName.startsWith("get")) { - String prop = getterName.substring(3); - return convertValidPropertyMethodSuffix(prop); + if (getterName.startsWith('get')) { + String prop = getterName.substring(3) + return convertValidPropertyMethodSuffix(prop) } - if (getterName.startsWith("is") && returnType.equals("boolean")) { - String prop = getterName.substring(2); - return convertValidPropertyMethodSuffix(prop); + if (getterName.startsWith('is') && returnType == 'boolean') { + String prop = getterName.substring(2) + return convertValidPropertyMethodSuffix(prop) } - return null; + return null } /** @@ -604,27 +599,28 @@ public static String getPropertyForGetter(String getterName, String returnType) * @param suffix The suffix to inspect * @return The property name or null */ + @PackageScope static String convertValidPropertyMethodSuffix(String suffix) { - if (suffix.length() == 0) return null; + if (suffix.isEmpty()) return null // We assume all characters are Character.isJavaIdentifierPart, but the first one may not be a valid // starting character. - if (!Character.isJavaIdentifierStart(suffix.charAt(0))) return null; + if (!Character.isJavaIdentifierStart(suffix.charAt(0))) return null if (suffix.length() == 1) { - return Character.isUpperCase(suffix.charAt(0)) ? suffix.toLowerCase() : null; + return Character.isUpperCase(suffix.charAt(0)) ? suffix.toLowerCase() : null } if (Character.isUpperCase(suffix.charAt(1))) { // "aProperty", "AProperty" - return suffix; + return suffix } if (Character.isUpperCase(suffix.charAt(0))) { - return Character.toLowerCase(suffix.charAt(0)) + suffix.substring(1); + return String.valueOf(Character.toLowerCase(suffix.charAt(0))) + suffix.substring(1) } if ('_' == suffix.charAt(0)) { - return suffix; + return suffix } - return null; + return null } /** @@ -637,8 +633,8 @@ static String convertValidPropertyMethodSuffix(String suffix) { * @deprecated use {@link #isGetter(String, Class, Class[])} instead because this method has a defect for "is.." method with Boolean return types. */ @Deprecated - public static boolean isGetter(String name, Class[] args) { - return isGetter(name, boolean.class, args); + static boolean isGetter(String name, Class[] args) { + return isGetter(name, boolean.class, args) } /** @@ -650,19 +646,19 @@ public static boolean isGetter(String name, Class[] args) { * @param args The arguments * @return true if it is a javabean property getter */ - public static boolean isGetter(String name, Class returnType, Class[] args) { - if (name == null || name.length() == 0 || args == null) return false; - if (args.length != 0) return false; + static boolean isGetter(String name, Class returnType, Class[] args) { + if (name == null || name.isEmpty() || args == null) return false + if (args.length != 0) return false - if (name.startsWith("get")) { - name = name.substring(3); - if (isPropertyMethodSuffix(name)) return true; + if (name.startsWith('get')) { + name = name.substring(3) + if (isPropertyMethodSuffix(name)) return true } - else if (name.startsWith("is") && returnType == boolean.class) { - name = name.substring(2); - if (isPropertyMethodSuffix(name)) return true; + else if (name.startsWith('is') && returnType == boolean.class) { + name = name.substring(2) + if (isPropertyMethodSuffix(name)) return true } - return false; + return false } /** @@ -706,10 +702,10 @@ else if (name.startsWith("is") && returnType == boolean.class) { * @return true if suffix indicates a property name */ protected static boolean isPropertyMethodSuffix(String suffix) { - if (suffix.length() == 0) return false; - if (!Character.isJavaIdentifierStart(suffix.charAt(0))) return false; - if (suffix.length() == 1) return Character.isUpperCase(suffix.charAt(0)); - return Character.isUpperCase(suffix.charAt(0)) || Character.isUpperCase(suffix.charAt(1)); + if (suffix.isEmpty()) return false + if (!Character.isJavaIdentifierStart(suffix.charAt(0))) return false + if (suffix.length() == 1) return Character.isUpperCase(suffix.charAt(0)) + return Character.isUpperCase(suffix.charAt(0)) || Character.isUpperCase(suffix.charAt(1)) } /** @@ -719,13 +715,14 @@ protected static boolean isPropertyMethodSuffix(String suffix) { * @param setterName The setter name, must be null or empty or a valid identifier name * @return The property name equivalent */ - public static String getPropertyForSetter(String setterName) { - if (setterName == null || setterName.length() == 0) return null; + static String getPropertyForSetter(String setterName) { + if (setterName == null || setterName.isEmpty()) return null - if (setterName.startsWith("set")) { - String prop = setterName.substring(3); - return convertValidPropertyMethodSuffix(prop); + if (setterName.startsWith('set')) { + String prop = setterName.substring(3) + return convertValidPropertyMethodSuffix(prop) } - return null; + return null } + } diff --git a/grails-gradle/model/src/main/groovy/grails/util/Holder.java b/grails-gradle/model/src/main/groovy/grails/util/Holder.groovy similarity index 63% rename from grails-gradle/model/src/main/groovy/grails/util/Holder.java rename to grails-gradle/model/src/main/groovy/grails/util/Holder.groovy index f5696b1e187..22bd872110f 100644 --- a/grails-gradle/model/src/main/groovy/grails/util/Holder.java +++ b/grails-gradle/model/src/main/groovy/grails/util/Holder.groovy @@ -16,68 +16,71 @@ * specific language governing permissions and limitations * under the License. */ -package grails.util; +package grails.util -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentHashMap + +import groovy.transform.CompileStatic /** * @author Burt Beckwith * @since 2.0 */ -public class Holder { +@CompileStatic +class Holder { - private Map instances = new ConcurrentHashMap<>(); + private Map instances = new ConcurrentHashMap<>() // TODO remove mappedOnly and singleton - private T singleton; - private String name; + private T singleton + private String name - public Holder(String name) { - this.name = name; + Holder(String name) { + this.name = name } - public T get() { - return get(false); + T get() { + return get(false) } - public T get(boolean mappedOnly) { - T t = instances.get(getClassLoaderId()); + T get(boolean mappedOnly) { + T t = instances[getClassLoaderId()] if (t != null) { - return t; + return t } - t = lookupSecondary(); + t = lookupSecondary() if (t != null) { - return t; + return t } // t = instances.get(System.identityHashCode(getClass().getClassLoader())); if (!mappedOnly) { - t = singleton; + t = singleton } - return t; + return t } protected T lookupSecondary() { // override in subclass if needed - return null; + return null } - public void set(T t) { - int id = getClassLoaderId(); - int thisClassLoaderId = System.identityHashCode(getClass().getClassLoader()); + void set(T t) { + int id = getClassLoaderId() + int thisClassLoaderId = System.identityHashCode(getClass().classLoader) if (t == null) { - instances.remove(id); - instances.remove(thisClassLoaderId); + instances.remove(id) + instances.remove(thisClassLoaderId) } else { - instances.put(id, t); - instances.put(thisClassLoaderId, t); + instances.put(id, t) + instances.put(thisClassLoaderId, t) } - singleton = t; + singleton = t } private int getClassLoaderId() { - return Environment.isWarDeployed() ? System.identityHashCode(Thread.currentThread().getContextClassLoader()) : System.identityHashCode(getClass().getClassLoader()); + return Environment.isWarDeployed() ? System.identityHashCode(Thread.currentThread().contextClassLoader) : System.identityHashCode(getClass().classLoader) } + } diff --git a/grails-gradle/model/src/main/groovy/grails/util/SupplierUtil.java b/grails-gradle/model/src/main/groovy/grails/util/SupplierUtil.groovy similarity index 64% rename from grails-gradle/model/src/main/groovy/grails/util/SupplierUtil.java rename to grails-gradle/model/src/main/groovy/grails/util/SupplierUtil.groovy index 9761c0e191d..75486764af8 100644 --- a/grails-gradle/model/src/main/groovy/grails/util/SupplierUtil.java +++ b/grails-gradle/model/src/main/groovy/grails/util/SupplierUtil.groovy @@ -16,10 +16,11 @@ * specific language governing permissions and limitations * under the License. */ -package grails.util; +package grails.util -import java.util.Optional; -import java.util.function.Supplier; +import java.util.function.Supplier + +import groovy.transform.CompileStatic /** * Helper methods for dealing with {@link Supplier}. @@ -27,7 +28,8 @@ * @author James Kleeh * @since 1.0 */ -public class SupplierUtil { +@CompileStatic +class SupplierUtil { /** * Caches the result of supplier in a thread safe manner. @@ -36,24 +38,24 @@ public class SupplierUtil { * @param The type of result * @return A new supplier that will cache the result */ - public static Supplier memoized(Supplier actual) { - return new Supplier<>() { - Supplier delegate = this::initialize; - boolean initialized; + static Supplier memoized(Supplier actual) { + return new Supplier() { + Supplier delegate = { -> initialize() } as Supplier + boolean initialized - public T get() { - return delegate.get(); + T get() { + return delegate.get() } private synchronized T initialize() { if (!initialized) { - T value = actual.get(); - delegate = () -> value; - initialized = true; + T value = actual.get() + delegate = { -> value } as Supplier + initialized = true } - return delegate.get(); + return delegate.get() } - }; + } } /** @@ -64,31 +66,32 @@ private synchronized T initialize() { * @param The type of result * @return A new supplier that will cache the result */ - public static Supplier memoizedNonEmpty(Supplier actual) { - return new Supplier<>() { - Supplier delegate = this::initialize; - boolean initialized; + static Supplier memoizedNonEmpty(Supplier actual) { + return new Supplier() { + Supplier delegate = { -> initialize() } as Supplier + boolean initialized - public T get() { - return delegate.get(); + T get() { + return delegate.get() } private synchronized T initialize() { if (!initialized) { - T value = actual.get(); + T value = actual.get() if (value == null) { - return null; + return null } if (value instanceof Optional) { if (!((Optional) value).isPresent()) { - return value; + return value } } - delegate = () -> value; - initialized = true; + delegate = { -> value } as Supplier + initialized = true } - return delegate.get(); + return delegate.get() } - }; + } } + } diff --git a/grails-gradle/model/src/main/groovy/org/grails/build/parsing/CommandLine.java b/grails-gradle/model/src/main/groovy/org/grails/build/parsing/CommandLine.groovy similarity index 62% rename from grails-gradle/model/src/main/groovy/org/grails/build/parsing/CommandLine.java rename to grails-gradle/model/src/main/groovy/org/grails/build/parsing/CommandLine.groovy index d37fe57888d..be6b0903a93 100644 --- a/grails-gradle/model/src/main/groovy/org/grails/build/parsing/CommandLine.java +++ b/grails-gradle/model/src/main/groovy/org/grails/build/parsing/CommandLine.groovy @@ -16,11 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.grails.build.parsing; - -import java.util.List; -import java.util.Map; -import java.util.Properties; +package org.grails.build.parsing /** * Represents the parsed command line options. @@ -28,94 +24,95 @@ * @author Graeme Rocher * @since 2.0 */ -public interface CommandLine { - - String DEBUG_FORK = "debug-fork"; - String OFFLINE_ARGUMENT = "offline"; - String VERBOSE_ARGUMENT = "verbose"; - String STACKTRACE_ARGUMENT = "stacktrace"; - String AGENT_ARGUMENT = "reloading"; - String VERSION_ARGUMENT = "version"; - String REFRESH_DEPENDENCIES_ARGUMENT = "refresh-dependencies"; - String HELP_ARGUMENT = "help"; - String NOANSI_ARGUMENT = "plain-output"; - String NON_INTERACTIVE_ARGUMENT = "non-interactive"; +interface CommandLine { + + String DEBUG_FORK = 'debug-fork' + String OFFLINE_ARGUMENT = 'offline' + String VERBOSE_ARGUMENT = 'verbose' + String STACKTRACE_ARGUMENT = 'stacktrace' + String AGENT_ARGUMENT = 'reloading' + String VERSION_ARGUMENT = 'version' + String REFRESH_DEPENDENCIES_ARGUMENT = 'refresh-dependencies' + String HELP_ARGUMENT = 'help' + String NOANSI_ARGUMENT = 'plain-output' + String NON_INTERACTIVE_ARGUMENT = 'non-interactive' /** * @return The environment specified */ - String getEnvironment(); + String getEnvironment() /** * @return Whether the environment is user specified */ - boolean isEnvironmentSet(); + boolean isEnvironmentSet() /** * @return The command name specified */ - String getCommandName(); + String getCommandName() /** * @return The remaining arguments after the command name */ - List getRemainingArgs(); + List getRemainingArgs() /** * @return The remaining arguments as an array */ - String[] getRemainingArgsArray(); + String[] getRemainingArgsArray() /** * @return The system properties specified */ - Properties getSystemProperties(); + Properties getSystemProperties() /** * @param name The name of the option * @return Whether the given option is specified */ - boolean hasOption(String name); + boolean hasOption(String name) /** * The value of an option * @param name The option * @return The value */ - Object optionValue(String name); + Object optionValue(String name) /** * @return The last specified option */ - Map.Entry lastOption(); + Map.Entry lastOption() /** * @return The remaining args as one big string */ - String getRemainingArgsString(); + String getRemainingArgsString() /** * @return The remaining args as one big string without undeclared options */ - String getRemainingArgsWithOptionsString(); + String getRemainingArgsWithOptionsString() /** * @return The remaining args separated by the line separator char */ - String getRemainingArgsLineSeparated(); + String getRemainingArgsLineSeparated() - Map getUndeclaredOptions(); + Map getUndeclaredOptions() - String lookupEnvironmentForCommand(); + String lookupEnvironmentForCommand() - void setCommand(String scriptName); + void setCommand(String scriptName) /** * Parses a new {@link CommandLine} instance that combines this instance with the given arguments * @param args The arguments * @return A new {@link CommandLine} instance */ - CommandLine parseNew(String[] args); + CommandLine parseNew(String[] args) + + String[] getRawArguments() - String[] getRawArguments(); } diff --git a/grails-gradle/model/src/main/groovy/org/grails/build/parsing/CommandLineParser.groovy b/grails-gradle/model/src/main/groovy/org/grails/build/parsing/CommandLineParser.groovy new file mode 100644 index 00000000000..14c189548cc --- /dev/null +++ b/grails-gradle/model/src/main/groovy/org/grails/build/parsing/CommandLineParser.groovy @@ -0,0 +1,298 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.grails.build.parsing + +import groovy.transform.CompileStatic +import groovy.transform.PackageScope + +import grails.util.Environment + +/** + * Command line parser that parses arguments to the command line. Written as a + * replacement for Commons CLI because it doesn't support unknown arguments and + * requires all arguments to be declared up front. + * + * It also doesn't support command options with hyphens. This class gets around those problems. + * + * @author Graeme Rocher + * @since 2.0 + */ +@CompileStatic +class CommandLineParser { + + @PackageScope + static Map ENV_ARGS = new HashMap<>() + + @PackageScope + static Map DEFAULT_ENVS = new HashMap<>() + private static CommandLine CURRENT = null + private static final String DEFAULT_PADDING = ' ' + + static { + ENV_ARGS.put('dev', Environment.DEVELOPMENT.getName()) + ENV_ARGS.put('prod', Environment.PRODUCTION.getName()) + ENV_ARGS.put('test', Environment.TEST.getName()) + DEFAULT_ENVS.put('war', Environment.PRODUCTION.getName()) + DEFAULT_ENVS.put('test-app', Environment.TEST.getName()) + } + + private Map declaredOptions = new HashMap<>() + private int longestOptionNameLength = 0 + private String usageMessage + + static CommandLine getCurrentCommandLine() { + return CURRENT + } + + /** + * Adds a declared option + * + * @param name The name of the option + * @param description The description + */ + void addOption(String name, String description) { + int length = name.length() + if (length > longestOptionNameLength) { + longestOptionNameLength = length + } + declaredOptions.put(name, new Option(name, description)) + } + + /** + * Parses a string of all the command line options converting them into an array of arguments to pass to #parse(String..args) + * + * @param string The string + * @return The command line + */ + CommandLine parseString(String string) { + // Steal ants implementation for argument splitting. Handles quoted arguments with " or '. + // Doesn't handle escape sequences with \ + return parse(translateCommandline(string)) + } + + /** + * Crack a command line. + * @param toProcess the command line to process. + * @return the command line broken into strings. + * An empty or null toProcess parameter results in a zero sized array. + */ + static String[] translateCommandline(String toProcess) { + if (toProcess == null || toProcess.isEmpty()) { + //no command? no string + return new String[0] + } + // parse with a simple finite state machine + + final int normal = 0 + final int inQuote = 1 + final int inDoubleQuote = 2 + int state = normal + final StringTokenizer tok = new StringTokenizer(toProcess, "\"\' ", true) + final ArrayList result = new ArrayList<>() + final StringBuilder current = new StringBuilder() + boolean lastTokenHasBeenQuoted = false + + while (tok.hasMoreTokens()) { + String nextTok = tok.nextToken() + switch (state) { + case inQuote: + if (nextTok == "\'") { + lastTokenHasBeenQuoted = true + state = normal + } else { + current << nextTok + } + break + case inDoubleQuote: + if (nextTok == '"') { + lastTokenHasBeenQuoted = true + state = normal + } else { + current << nextTok + } + break + default: + if (nextTok == "\'") { + state = inQuote + } else if (nextTok == '"') { + state = inDoubleQuote + } else if (nextTok == ' ') { + if (lastTokenHasBeenQuoted || !current.isEmpty()) { + result.add(current.toString()) + current.setLength(0) + } + } else { + current << nextTok + } + lastTokenHasBeenQuoted = false + break + } + } + if (lastTokenHasBeenQuoted || !current.isEmpty()) { + result.add(current.toString()) + } + if (state == inQuote || state == inDoubleQuote) { + throw new ParseException('unbalanced quotes in ' + toProcess) + } + return result.toArray(new String[result.size()]) + } + + /** + * Parses a string of all the command line options converting them into an array of arguments to pass to #parse(String..args) + * + * @param commandName The command name + * @param args The string + * @return The command line + */ + CommandLine parseString(String commandName, String args) { + // Steal ants implementation for argument splitting. Handles quoted arguments with " or '. + // Doesn't handle escape sequences with \ + String[] argArray = translateCommandline(args) + DefaultCommandLine cl = createCommandLine() + cl.setCommandName(commandName) + parseInternal(cl, argArray, false) + return cl + } + + /** + * Parses the given list of command line arguments. Arguments starting with -D become system properties, + * arguments starting with -- or - become either declared or undeclared options. All other arguments are + * put into a list of remaining arguments + * + * @param args The arguments + * @return The command line state + */ + CommandLine parse(String... args) { + DefaultCommandLine cl = createCommandLine() + return parse(cl, args) + } + + CommandLine parse(DefaultCommandLine cl, String[] args) { + parseInternal(cl, args, true) + return cl + } + + private void parseInternal(DefaultCommandLine cl, String[] args, boolean firstArgumentIsCommand) { + cl.setRawArguments(args) + String lastWasOption = null + for (String arg in args) { + if (arg == null) continue + String trimmed = arg.trim() + if (trimmed != null && !trimmed.isEmpty()) { + if (trimmed.charAt(0) == '"' && trimmed.charAt(trimmed.length() - 1) == '"') { + trimmed = trimmed.substring(1, trimmed.length() - 1) + } + if (trimmed.charAt(0) == '-') { + lastWasOption = processOption(cl, trimmed) + } + else { + if (lastWasOption != null) { + cl.addUndeclaredOption(lastWasOption, trimmed) + lastWasOption = null + continue + } + if (firstArgumentIsCommand && ENV_ARGS.containsKey(trimmed)) { + cl.setEnvironment(ENV_ARGS[trimmed]) + } + else { + if (firstArgumentIsCommand) { + cl.setCommandName(trimmed) + firstArgumentIsCommand = false + } + else { + cl.addRemainingArg(trimmed) + } + } + } + } + } + } + + String getOptionsHelpMessage() { + String ls = System.getProperty('line.separator') + usageMessage = 'Available options:' + StringBuilder sb = new StringBuilder(usageMessage) + sb << ls + for (Option option in declaredOptions.values()) { + String name = option.name + int extraPadding = longestOptionNameLength - name.length() + sb << ' -' << name + for (int i = 0; i < extraPadding; i++) { + sb << ' ' + } + sb << DEFAULT_PADDING << option.description << ls + } + + return sb.toString() + } + + protected DefaultCommandLine createCommandLine() { + DefaultCommandLine defaultCommandLine = new DefaultCommandLine() + CURRENT = defaultCommandLine + return defaultCommandLine + } + + protected String processOption(DefaultCommandLine cl, String arg) { + if (arg.length() < 2) { + return null + } + + if (arg.charAt(1) == 'D' && arg.contains('=')) { + processSystemArg(cl, arg) + return null + } + + arg = (arg.charAt(1) == '-' ? arg.substring(2, arg.length()) : arg.substring(1, arg.length())).trim() + + if (arg.contains('=')) { + String[] split = arg.split('=') + String name = split[0].trim() + validateOptionName(name) + String value = split[1].trim() + if (declaredOptions.containsKey(name)) { + cl.addDeclaredOption(name, declaredOptions[name], value) + } + else { + cl.addUndeclaredOption(name, value) + } + return null + } + + validateOptionName(arg) + if (declaredOptions.containsKey(arg)) { + cl.addDeclaredOption(arg, declaredOptions[arg]) + } + else { + cl.addUndeclaredOption(arg) + } + return arg + } + + private void validateOptionName(String name) { + if (name.contains(' ')) throw new ParseException('Invalid argument: ' + name) + } + + protected void processSystemArg(DefaultCommandLine cl, String arg) { + int i = arg.indexOf('=') + String name = arg.substring(2, i) + String value = arg.substring(i + 1, arg.length()) + cl.addSystemProperty(name, value) + } + +} diff --git a/grails-gradle/model/src/main/groovy/org/grails/build/parsing/CommandLineParser.java b/grails-gradle/model/src/main/groovy/org/grails/build/parsing/CommandLineParser.java deleted file mode 100644 index 0e4d522e0d0..00000000000 --- a/grails-gradle/model/src/main/groovy/org/grails/build/parsing/CommandLineParser.java +++ /dev/null @@ -1,295 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.grails.build.parsing; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; -import java.util.StringTokenizer; - -import grails.util.Environment; - -/** - * Command line parser that parses arguments to the command line. Written as a - * replacement for Commons CLI because it doesn't support unknown arguments and - * requires all arguments to be declared up front. - * - * It also doesn't support command options with hyphens. This class gets around those problems. - * - * @author Graeme Rocher - * @since 2.0 - */ -public class CommandLineParser { - - static Map ENV_ARGS = new HashMap<>(); - static Map DEFAULT_ENVS = new HashMap<>(); - private static CommandLine CURRENT = null; - private static final String DEFAULT_PADDING = " "; - - static { - ENV_ARGS.put("dev", Environment.DEVELOPMENT.getName()); - ENV_ARGS.put("prod", Environment.PRODUCTION.getName()); - ENV_ARGS.put("test", Environment.TEST.getName()); - DEFAULT_ENVS.put("war", Environment.PRODUCTION.getName()); - DEFAULT_ENVS.put("test-app", Environment.TEST.getName()); - } - - private Map declaredOptions = new HashMap<>(); - private int longestOptionNameLength = 0; - private String usageMessage; - - public static CommandLine getCurrentCommandLine() { - return CURRENT; - } - - /** - * Adds a declared option - * - * @param name The name of the option - * @param description The description - */ - public void addOption(String name, String description) { - int length = name.length(); - if (length > longestOptionNameLength) { - longestOptionNameLength = length; - } - declaredOptions.put(name, new Option(name, description)); - } - - /** - * Parses a string of all the command line options converting them into an array of arguments to pass to #parse(String..args) - * - * @param string The string - * @return The command line - */ - public CommandLine parseString(String string) { - // Steal ants implementation for argument splitting. Handles quoted arguments with " or '. - // Doesn't handle escape sequences with \ - return parse(translateCommandline(string)); - } - - /** - * Crack a command line. - * @param toProcess the command line to process. - * @return the command line broken into strings. - * An empty or null toProcess parameter results in a zero sized array. - */ - public static String[] translateCommandline(String toProcess) { - if (toProcess == null || toProcess.length() == 0) { - //no command? no string - return new String[0]; - } - // parse with a simple finite state machine - - final int normal = 0; - final int inQuote = 1; - final int inDoubleQuote = 2; - int state = normal; - final StringTokenizer tok = new StringTokenizer(toProcess, "\"\' ", true); - final ArrayList result = new ArrayList<>(); - final StringBuilder current = new StringBuilder(); - boolean lastTokenHasBeenQuoted = false; - - while (tok.hasMoreTokens()) { - String nextTok = tok.nextToken(); - switch (state) { - case inQuote: - if ("\'".equals(nextTok)) { - lastTokenHasBeenQuoted = true; - state = normal; - } else { - current.append(nextTok); - } - break; - case inDoubleQuote: - if ("\"".equals(nextTok)) { - lastTokenHasBeenQuoted = true; - state = normal; - } else { - current.append(nextTok); - } - break; - default: - if ("\'".equals(nextTok)) { - state = inQuote; - } else if ("\"".equals(nextTok)) { - state = inDoubleQuote; - } else if (" ".equals(nextTok)) { - if (lastTokenHasBeenQuoted || current.length() != 0) { - result.add(current.toString()); - current.setLength(0); - } - } else { - current.append(nextTok); - } - lastTokenHasBeenQuoted = false; - break; - } - } - if (lastTokenHasBeenQuoted || current.length() != 0) { - result.add(current.toString()); - } - if (state == inQuote || state == inDoubleQuote) { - throw new ParseException("unbalanced quotes in " + toProcess); - } - return result.toArray(new String[result.size()]); - } - - /** - * Parses a string of all the command line options converting them into an array of arguments to pass to #parse(String..args) - * - * @param commandName The command name - * @param args The string - * @return The command line - */ - public CommandLine parseString(String commandName, String args) { - // Steal ants implementation for argument splitting. Handles quoted arguments with " or '. - // Doesn't handle escape sequences with \ - String[] argArray = translateCommandline(args); - DefaultCommandLine cl = createCommandLine(); - cl.setCommandName(commandName); - parseInternal(cl, argArray, false); - return cl; - } - - /** - * Parses the given list of command line arguments. Arguments starting with -D become system properties, - * arguments starting with -- or - become either declared or undeclared options. All other arguments are - * put into a list of remaining arguments - * - * @param args The arguments - * @return The command line state - */ - public CommandLine parse(String... args) { - DefaultCommandLine cl = createCommandLine(); - return parse(cl, args); - } - - public CommandLine parse(DefaultCommandLine cl, String[] args) { - parseInternal(cl, args, true); - return cl; - } - - private void parseInternal(DefaultCommandLine cl, String[] args, boolean firstArgumentIsCommand) { - cl.setRawArguments(args); - String lastWasOption = null; - for (String arg : args) { - if (arg == null) continue; - String trimmed = arg.trim(); - if (trimmed != null && trimmed.length() > 0) { - if (trimmed.charAt(0) == '"' && trimmed.charAt(trimmed.length() - 1) == '"') { - trimmed = trimmed.substring(1, trimmed.length() - 1); - } - if (trimmed.charAt(0) == '-') { - lastWasOption = processOption(cl, trimmed); - } - else { - if (lastWasOption != null) { - cl.addUndeclaredOption(lastWasOption, trimmed); - lastWasOption = null; - continue; - } - if (firstArgumentIsCommand && ENV_ARGS.containsKey(trimmed)) { - cl.setEnvironment(ENV_ARGS.get(trimmed)); - } - else { - if (firstArgumentIsCommand) { - cl.setCommandName(trimmed); - firstArgumentIsCommand = false; - } - else { - cl.addRemainingArg(trimmed); - } - } - } - } - } - } - - public String getOptionsHelpMessage() { - String ls = System.getProperty("line.separator"); - usageMessage = "Available options:"; - StringBuilder sb = new StringBuilder(usageMessage); - sb.append(ls); - for (Option option : declaredOptions.values()) { - String name = option.getName(); - int extraPadding = longestOptionNameLength - name.length(); - sb.append(" -").append(name); - for (int i = 0; i < extraPadding; i++) { - sb.append(' '); - } - sb.append(DEFAULT_PADDING).append(option.getDescription()).append(ls); - } - - return sb.toString(); - } - - protected DefaultCommandLine createCommandLine() { - DefaultCommandLine defaultCommandLine = new DefaultCommandLine(); - CURRENT = defaultCommandLine; - return defaultCommandLine; - } - - protected String processOption(DefaultCommandLine cl, String arg) { - if (arg.length() < 2) { - return null; - } - - if (arg.charAt(1) == 'D' && arg.contains("=")) { - processSystemArg(cl, arg); - return null; - } - - arg = (arg.charAt(1) == '-' ? arg.substring(2, arg.length()) : arg.substring(1, arg.length())).trim(); - - if (arg.contains("=")) { - String[] split = arg.split("="); - String name = split[0].trim(); - validateOptionName(name); - String value = split[1].trim(); - if (declaredOptions.containsKey(name)) { - cl.addDeclaredOption(name, declaredOptions.get(name), value); - } - else { - cl.addUndeclaredOption(name, value); - } - return null; - } - - validateOptionName(arg); - if (declaredOptions.containsKey(arg)) { - cl.addDeclaredOption(arg, declaredOptions.get(arg)); - } - else { - cl.addUndeclaredOption(arg); - } - return arg; - } - - private void validateOptionName(String name) { - if (name.contains(" ")) throw new ParseException("Invalid argument: " + name); - } - - protected void processSystemArg(DefaultCommandLine cl, String arg) { - int i = arg.indexOf("="); - String name = arg.substring(2, i); - String value = arg.substring(i + 1, arg.length()); - cl.addSystemProperty(name, value); - } -} diff --git a/grails-gradle/model/src/main/groovy/org/grails/build/parsing/DefaultCommandLine.groovy b/grails-gradle/model/src/main/groovy/org/grails/build/parsing/DefaultCommandLine.groovy new file mode 100644 index 00000000000..bc519ed1640 --- /dev/null +++ b/grails-gradle/model/src/main/groovy/org/grails/build/parsing/DefaultCommandLine.groovy @@ -0,0 +1,230 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.grails.build.parsing + +import groovy.transform.CompileStatic +import groovy.transform.PackageScope + +import grails.util.Environment + +/** + * Implementation of the {@link CommandLine} interface. + * + * @author Graeme Rocher + * @since 2.0 + */ +@CompileStatic +class DefaultCommandLine implements CommandLine { + + @PackageScope Properties systemProperties = new Properties() + @PackageScope LinkedHashMap undeclaredOptions = new LinkedHashMap<>() + @PackageScope LinkedHashMap declaredOptions = new LinkedHashMap<>() + @PackageScope List remainingArgs = new ArrayList<>() + private String environment + private String commandName + private String[] rawArguments + + void addDeclaredOption(String name, Option option) { + addDeclaredOption(name, option, Boolean.TRUE) + } + + void addUndeclaredOption(String option) { + undeclaredOptions.put(option, Boolean.TRUE) + } + + void addUndeclaredOption(String option, Object value) { + undeclaredOptions.put(option, value) + } + + void addDeclaredOption(String name, Option option, Object value) { + SpecifiedOption so = new SpecifiedOption() + so.option = option + so.value = value + + declaredOptions.put(name, so) + } + + @Override + CommandLine parseNew(String[] args) { + DefaultCommandLine defaultCommandLine = new DefaultCommandLine() + defaultCommandLine.systemProperties.putAll(systemProperties) + defaultCommandLine.undeclaredOptions.putAll(undeclaredOptions) + defaultCommandLine.declaredOptions.putAll(declaredOptions) + CommandLineParser parser = new CommandLineParser() + return parser.parse(defaultCommandLine, args) + } + + void setEnvironment(String environment) { + this.environment = environment + System.setProperty(Environment.KEY, environment) + } + + void setCommand(String name) { + commandName = name + } + + String getEnvironment() { + boolean useDefaultEnv = environment == null + String env + if (useDefaultEnv && commandName != null) { + env = lookupEnvironmentForCommand() + } + else { + String fallbackEnv = System.getProperty(Environment.KEY) != null ? System.getProperty(Environment.KEY) : Environment.DEVELOPMENT.getName() + env = environment != null ? environment : fallbackEnv + } + + System.setProperty(Environment.KEY, env) + System.setProperty(Environment.DEFAULT, String.valueOf(useDefaultEnv)) + + return env + } + + String lookupEnvironmentForCommand() { + String fallbackEnv = System.getProperty(Environment.KEY) != null ? System.getProperty(Environment.KEY) : Environment.DEVELOPMENT.getName() + String env = CommandLineParser.DEFAULT_ENVS[commandName] + return env == null ? fallbackEnv : env + } + + boolean isEnvironmentSet() { + return environment != null + } + + void setCommandName(String cmd) { + if (REFRESH_DEPENDENCIES_ARGUMENT == cmd) { + addUndeclaredOption(REFRESH_DEPENDENCIES_ARGUMENT) + } + commandName = cmd + } + + String getCommandName() { + return commandName + } + + void addRemainingArg(String arg) { + remainingArgs.add(arg) + } + + List getRemainingArgs() { + return remainingArgs + } + + String[] getRemainingArgsArray() { + return remainingArgs.toArray(new String[remainingArgs.size()]) + } + + Properties getSystemProperties() { + return systemProperties + } + + boolean hasOption(String name) { + return declaredOptions.containsKey(name) || undeclaredOptions.containsKey(name) + } + + Object optionValue(String name) { + if (declaredOptions.containsKey(name)) { + SpecifiedOption specifiedOption = declaredOptions[name] + return specifiedOption.value + } + if (undeclaredOptions.containsKey(name)) { + return undeclaredOptions[name] + } + return null + } + + @Override + Map.Entry lastOption() { + final Iterator> i = undeclaredOptions.entrySet().iterator() + while (i.hasNext()) { + Map.Entry next = i.next() + if (!i.hasNext()) { + return next + } + } + return null + } + + String getRemainingArgsString() { + return remainingArgsToString(' ', false) + } + + @Override + String getRemainingArgsWithOptionsString() { + return remainingArgsToString(' ', true) + } + + String getRemainingArgsLineSeparated() { + return remainingArgsToString('\n', false) + } + + private String remainingArgsToString(String separator, boolean includeOptions) { + StringBuilder sb = new StringBuilder() + String sep = '' + List args = new ArrayList<>(remainingArgs) + if (includeOptions) { + for (Map.Entry entry in undeclaredOptions.entrySet()) { + if (entry.value instanceof Boolean && ((Boolean) entry.value)) { + args.add('-' + entry.key) + } + else { + args.add('-' + entry.key + '=' + entry.value) + } + } + } + for (String arg in args) { + sb << sep << arg + sep = separator + } + return sb.toString() + } + + Map getUndeclaredOptions() { + return undeclaredOptions + } + + void addSystemProperty(String name, String value) { + if (name == Environment.KEY) { + setEnvironment(value) + } + systemProperties.put(name, value) + } + + void setRawArguments(String[] args) { + this.rawArguments = args + } + + @Override + String[] getRawArguments() { + return rawArguments + } + + static class SpecifiedOption { + private Option option + private Object value + + Option getOption() { + return option + } + + Object getValue() { + return value + } + } + +} diff --git a/grails-gradle/model/src/main/groovy/org/grails/build/parsing/DefaultCommandLine.java b/grails-gradle/model/src/main/groovy/org/grails/build/parsing/DefaultCommandLine.java deleted file mode 100644 index a4527054f56..00000000000 --- a/grails-gradle/model/src/main/groovy/org/grails/build/parsing/DefaultCommandLine.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.grails.build.parsing; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; - -import grails.util.Environment; - -/** - * Implementation of the {@link CommandLine} interface. - * - * @author Graeme Rocher - * @since 2.0 - */ -public class DefaultCommandLine implements CommandLine { - - Properties systemProperties = new Properties(); - LinkedHashMap undeclaredOptions = new LinkedHashMap<>(); - LinkedHashMap declaredOptions = new LinkedHashMap<>(); - List remainingArgs = new ArrayList<>(); - private String environment; - private String commandName; - private String[] rawArguments; - - public void addDeclaredOption(String name, Option option) { - addDeclaredOption(name, option, Boolean.TRUE); - } - - public void addUndeclaredOption(String option) { - undeclaredOptions.put(option, Boolean.TRUE); - } - - public void addUndeclaredOption(String option, Object value) { - undeclaredOptions.put(option, value); - } - - public void addDeclaredOption(String name, Option option, Object value) { - SpecifiedOption so = new SpecifiedOption(); - so.option = option; - so.value = value; - - declaredOptions.put(name, so); - } - - @Override - public CommandLine parseNew(String[] args) { - DefaultCommandLine defaultCommandLine = new DefaultCommandLine(); - defaultCommandLine.systemProperties.putAll(systemProperties); - defaultCommandLine.undeclaredOptions.putAll(undeclaredOptions); - defaultCommandLine.declaredOptions.putAll(declaredOptions); - CommandLineParser parser = new CommandLineParser(); - return parser.parse(defaultCommandLine, args); - } - - public void setEnvironment(String environment) { - this.environment = environment; - System.setProperty(Environment.KEY, environment); - } - - public void setCommand(String name) { - commandName = name; - } - - public String getEnvironment() { - boolean useDefaultEnv = environment == null; - String env; - if (useDefaultEnv && commandName != null) { - env = lookupEnvironmentForCommand(); - } - else { - String fallbackEnv = System.getProperty(Environment.KEY) != null ? System.getProperty(Environment.KEY) : Environment.DEVELOPMENT.getName(); - env = environment != null ? environment : fallbackEnv; - } - - System.setProperty(Environment.KEY, env); - System.setProperty(Environment.DEFAULT, String.valueOf(useDefaultEnv)); - - return env; - } - - public String lookupEnvironmentForCommand() { - String fallbackEnv = System.getProperty(Environment.KEY) != null ? System.getProperty(Environment.KEY) : Environment.DEVELOPMENT.getName(); - String env = CommandLineParser.DEFAULT_ENVS.get(commandName); - return env == null ? fallbackEnv : env; - } - - public boolean isEnvironmentSet() { - return environment != null; - } - - public void setCommandName(String cmd) { - if (REFRESH_DEPENDENCIES_ARGUMENT.equals(cmd)) { - addUndeclaredOption(REFRESH_DEPENDENCIES_ARGUMENT); - } - commandName = cmd; - } - - public String getCommandName() { - return commandName; - } - - public void addRemainingArg(String arg) { - remainingArgs.add(arg); - } - - public List getRemainingArgs() { - return remainingArgs; - } - - public String[] getRemainingArgsArray() { - return remainingArgs.toArray(new String[remainingArgs.size()]); - } - - public Properties getSystemProperties() { - return systemProperties; - } - - public boolean hasOption(String name) { - return declaredOptions.containsKey(name) || undeclaredOptions.containsKey(name); - } - - public Object optionValue(String name) { - if (declaredOptions.containsKey(name)) { - SpecifiedOption specifiedOption = declaredOptions.get(name); - return specifiedOption.value; - } - if (undeclaredOptions.containsKey(name)) { - return undeclaredOptions.get(name); - } - return null; - } - - @Override - public Map.Entry lastOption() { - final Iterator> i = undeclaredOptions.entrySet().iterator(); - while (i.hasNext()) { - Map.Entry next = i.next(); - if (!i.hasNext()) { - return next; - } - } - return null; - } - - public String getRemainingArgsString() { - return remainingArgsToString(" ", false); - } - - @Override - public String getRemainingArgsWithOptionsString() { - return remainingArgsToString(" ", true); - } - - public String getRemainingArgsLineSeparated() { - return remainingArgsToString("\n", false); - } - - private String remainingArgsToString(String separator, boolean includeOptions) { - StringBuilder sb = new StringBuilder(); - String sep = ""; - List args = new ArrayList<>(remainingArgs); - if (includeOptions) { - for (Map.Entry entry : undeclaredOptions.entrySet()) { - if (entry.getValue() instanceof Boolean && ((Boolean) entry.getValue())) { - args.add('-' + entry.getKey()); - } - else { - args.add('-' + entry.getKey() + '=' + entry.getValue()); - } - } - } - for (String arg : args) { - sb.append(sep).append(arg); - sep = separator; - } - return sb.toString(); - } - - public Map getUndeclaredOptions() { - return undeclaredOptions; - } - - public void addSystemProperty(String name, String value) { - if (Environment.KEY.equals(name)) { - setEnvironment(value); - } - systemProperties.put(name, value); - } - - public void setRawArguments(String[] args) { - this.rawArguments = args; - } - - @Override - public String[] getRawArguments() { - return rawArguments; - } - - public static class SpecifiedOption { - private Option option; - private Object value; - - public Option getOption() { - return option; - } - - public Object getValue() { - return value; - } - } -} diff --git a/grails-gradle/model/src/main/groovy/org/grails/build/parsing/Option.java b/grails-gradle/model/src/main/groovy/org/grails/build/parsing/Option.groovy similarity index 64% rename from grails-gradle/model/src/main/groovy/org/grails/build/parsing/Option.java rename to grails-gradle/model/src/main/groovy/org/grails/build/parsing/Option.groovy index ec94c1c138d..5dda283f5c3 100644 --- a/grails-gradle/model/src/main/groovy/org/grails/build/parsing/Option.java +++ b/grails-gradle/model/src/main/groovy/org/grails/build/parsing/Option.groovy @@ -16,7 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -package org.grails.build.parsing; +package org.grails.build.parsing + +import groovy.transform.CompileStatic /** * Represents a command line option. @@ -24,25 +26,27 @@ * @author Graeme Rocher * @since 2.0 */ -public class Option { +@CompileStatic +class Option { - private String name; - private String description; + private String name + private String description - public Option(String name, String description) { - if (name == null || name.length() == 0) { - throw new IllegalArgumentException("illegal option specified"); + Option(String name, String description) { + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException('illegal option specified') } - this.name = name; - this.description = description == null ? "" : description; + this.name = name + this.description = description == null ? '' : description } - public String getName() { - return name; + String getName() { + return name } - public String getDescription() { - return description; + String getDescription() { + return description } + } diff --git a/grails-gradle/model/src/main/groovy/org/grails/build/parsing/ParseException.java b/grails-gradle/model/src/main/groovy/org/grails/build/parsing/ParseException.groovy similarity index 75% rename from grails-gradle/model/src/main/groovy/org/grails/build/parsing/ParseException.java rename to grails-gradle/model/src/main/groovy/org/grails/build/parsing/ParseException.groovy index cf8c73e9ad7..f8b9ca0f521 100644 --- a/grails-gradle/model/src/main/groovy/org/grails/build/parsing/ParseException.java +++ b/grails-gradle/model/src/main/groovy/org/grails/build/parsing/ParseException.groovy @@ -16,7 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -package org.grails.build.parsing; +package org.grails.build.parsing + +import groovy.transform.CompileStatic /** * Exception that is thrown in command line parsing fails. @@ -24,15 +26,17 @@ * @author Graeme Rocher * @since 2.0 */ -public class ParseException extends RuntimeException { +@CompileStatic +class ParseException extends RuntimeException { - private static final long serialVersionUID = 1; + private static final long serialVersionUID = 1 - public ParseException(String message) { - super(message); + ParseException(String message) { + super(message) } - public ParseException(Throwable cause) { - super(cause); + ParseException(Throwable cause) { + super(cause) } + } diff --git a/grails-gradle/model/src/main/groovy/org/grails/io/support/AbstractFileResolvingResource.java b/grails-gradle/model/src/main/groovy/org/grails/io/support/AbstractFileResolvingResource.groovy similarity index 65% rename from grails-gradle/model/src/main/groovy/org/grails/io/support/AbstractFileResolvingResource.java rename to grails-gradle/model/src/main/groovy/org/grails/io/support/AbstractFileResolvingResource.groovy index 443ba91fb86..cd4d4157ff0 100644 --- a/grails-gradle/model/src/main/groovy/org/grails/io/support/AbstractFileResolvingResource.java +++ b/grails-gradle/model/src/main/groovy/org/grails/io/support/AbstractFileResolvingResource.groovy @@ -16,15 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -package org.grails.io.support; +package org.grails.io.support -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.URI; -import java.net.URL; -import java.net.URLConnection; +import groovy.transform.CompileStatic /** * Abstract base class for resources which resolve URLs into File references, @@ -36,15 +30,16 @@ * @author Juergen Hoeller * @since 3.0 */ -public abstract class AbstractFileResolvingResource implements Resource { +@CompileStatic +abstract class AbstractFileResolvingResource implements Resource { /** * This implementation returns a File reference for the underlying class path * resource, provided that it refers to a file in the file system. */ - public File getFile() throws IOException { - URL url = getURL(); - return GrailsResourceUtils.getFile(url, getDescription()); + File getFile() throws IOException { + URL url = getURL() + return GrailsResourceUtils.getFile(url, getDescription()) } /** @@ -52,12 +47,12 @@ public File getFile() throws IOException { * (or jar file, in case of a resource in a jar/zip). */ protected File getFileForLastModifiedCheck() throws IOException { - URL url = getURL(); + URL url = getURL() if (GrailsResourceUtils.isJarURL(url)) { - URL actualUrl = GrailsResourceUtils.extractJarFileURL(url); - return GrailsResourceUtils.getFile(actualUrl, "Jar URL"); + URL actualUrl = GrailsResourceUtils.extractJarFileURL(url) + return GrailsResourceUtils.getFile(actualUrl, 'Jar URL') } - return getFile(); + return getFile() } /** @@ -65,7 +60,7 @@ protected File getFileForLastModifiedCheck() throws IOException { * resource, provided that it refers to a file in the file system. */ protected File getFile(URI uri) throws IOException { - return GrailsResourceUtils.getFile(uri, getDescription()); + return GrailsResourceUtils.getFile(uri, getDescription()) } /** @@ -75,93 +70,94 @@ protected File getFile(URI uri) throws IOException { * @param con the URLConnection to set the flag on */ private static void useCachesIfNecessary(URLConnection con) { - con.setUseCaches(con.getClass().getName().startsWith("JNLP")); + con.useCaches = con.getClass().name.startsWith('JNLP') } - public boolean exists() { + boolean exists() { try { - URL url = getURL(); + URL url = getURL() if (GrailsResourceUtils.isFileURL(url)) { // Proceed with file system resolution... - return getFile().exists(); + return getFile().exists() } // Try a URL connection content-length header... - URLConnection con = url.openConnection(); - useCachesIfNecessary(con); + URLConnection con = url.openConnection() + useCachesIfNecessary(con) HttpURLConnection httpCon = - (con instanceof HttpURLConnection ? (HttpURLConnection) con : null); + (con instanceof HttpURLConnection ? (HttpURLConnection) con : null) if (httpCon != null) { - httpCon.setRequestMethod("HEAD"); - int code = httpCon.getResponseCode(); + httpCon.requestMethod = 'HEAD' + int code = httpCon.responseCode if (code == HttpURLConnection.HTTP_OK) { - return true; + return true } if (code == HttpURLConnection.HTTP_NOT_FOUND) { - return false; + return false } } - if (con.getContentLength() >= 0) { - return true; + if (con.contentLength >= 0) { + return true } if (httpCon != null) { // no HTTP OK status, and no content-length header: give up - httpCon.disconnect(); - return false; + httpCon.disconnect() + return false } // Fall back to stream existence: can we open the stream? - InputStream is = getInputStream(); - is.close(); - return true; + InputStream is = getInputStream() + is.close() + return true } catch (IOException ex) { - return false; + return false } } - public boolean isReadable() { + boolean isReadable() { try { - URL url = getURL(); + URL url = getURL() if (GrailsResourceUtils.isFileURL(url)) { // Proceed with file system resolution... - File file = getFile(); - return (file.canRead() && !file.isDirectory()); + File file = getFile() + return (file.canRead() && !file.isDirectory()) } - return true; + return true } catch (IOException ex) { - return false; + return false } } - public long contentLength() throws IOException { - URL url = getURL(); + long contentLength() throws IOException { + URL url = getURL() if (GrailsResourceUtils.isFileURL(url)) { // Proceed with file system resolution... - return getFile().length(); + return getFile().length() } // Try a URL connection content-length header... - URLConnection con = url.openConnection(); - useCachesIfNecessary(con); + URLConnection con = url.openConnection() + useCachesIfNecessary(con) if (con instanceof HttpURLConnection) { - ((HttpURLConnection) con).setRequestMethod("HEAD"); + con.requestMethod = 'HEAD' } - return con.getContentLength(); + return con.contentLength } - public long lastModified() throws IOException { - URL url = getURL(); + long lastModified() throws IOException { + URL url = getURL() if (GrailsResourceUtils.isFileURL(url) || GrailsResourceUtils.isJarURL(url)) { // Proceed with file system resolution... - return getFile().lastModified(); + return getFile().lastModified() } // Try a URL connection last-modified header... - URLConnection con = url.openConnection(); - useCachesIfNecessary(con); + URLConnection con = url.openConnection() + useCachesIfNecessary(con) if (con instanceof HttpURLConnection) { - ((HttpURLConnection) con).setRequestMethod("HEAD"); + con.requestMethod = 'HEAD' } - return con.getLastModified(); + return con.lastModified } + } diff --git a/grails-gradle/model/src/main/groovy/org/grails/io/support/ClassPathResource.java b/grails-gradle/model/src/main/groovy/org/grails/io/support/ClassPathResource.groovy similarity index 65% rename from grails-gradle/model/src/main/groovy/org/grails/io/support/ClassPathResource.java rename to grails-gradle/model/src/main/groovy/org/grails/io/support/ClassPathResource.groovy index 4a3f089a7a4..7f991c9afe7 100644 --- a/grails-gradle/model/src/main/groovy/org/grails/io/support/ClassPathResource.java +++ b/grails-gradle/model/src/main/groovy/org/grails/io/support/ClassPathResource.groovy @@ -16,14 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -package org.grails.io.support; +package org.grails.io.support -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.net.URL; -import java.util.Arrays; +import groovy.transform.CompileStatic /** * Resource implementation for class path resources. @@ -38,13 +33,14 @@ * @see java.lang.ClassLoader#getResourceAsStream(String) * @see java.lang.Class#getResourceAsStream(String) */ -public class ClassPathResource extends AbstractFileResolvingResource { +@CompileStatic +class ClassPathResource extends AbstractFileResolvingResource { - private final String path; + private final String path - private ClassLoader classLoader; + private ClassLoader classLoader - private Class clazz; + private Class clazz /** * Create a new ClassPathResource for ClassLoader usage. @@ -55,8 +51,8 @@ public class ClassPathResource extends AbstractFileResolvingResource { * @param path the absolute path within the class path * @see java.lang.ClassLoader#getResourceAsStream(String) */ - public ClassPathResource(String path) { - this(path, (ClassLoader) null); + ClassPathResource(String path) { + this(path, (ClassLoader) null) } /** @@ -68,13 +64,13 @@ public ClassPathResource(String path) { * or null for the thread context class loader * @see java.lang.ClassLoader#getResourceAsStream(String) */ - public ClassPathResource(String path, ClassLoader classLoader) { - String pathToUse = GrailsResourceUtils.cleanPath(path); - if (pathToUse.startsWith("/")) { - pathToUse = pathToUse.substring(1); + ClassPathResource(String path, ClassLoader classLoader) { + String pathToUse = GrailsResourceUtils.cleanPath(path) + if (pathToUse.startsWith('/')) { + pathToUse = pathToUse.substring(1) } - this.path = pathToUse; - this.classLoader = classLoader == null ? DefaultResourceLoader.getDefaultClassLoader() : classLoader; + this.path = pathToUse + this.classLoader = classLoader == null ? DefaultResourceLoader.getDefaultClassLoader() : classLoader } /** @@ -85,9 +81,9 @@ public ClassPathResource(String path, ClassLoader classLoader) { * @param clazz the class to load resources with * @see java.lang.Class#getResourceAsStream */ - public ClassPathResource(String path, Class clazz) { - this.path = GrailsResourceUtils.cleanPath(path); - this.clazz = clazz; + ClassPathResource(String path, Class clazz) { + this.path = GrailsResourceUtils.cleanPath(path) + this.clazz = clazz } /** @@ -98,23 +94,23 @@ public ClassPathResource(String path, Class clazz) { * @param clazz the class to load resources with, if any */ protected ClassPathResource(String path, ClassLoader classLoader, Class clazz) { - this.path = GrailsResourceUtils.cleanPath(path); - this.classLoader = classLoader; - this.clazz = clazz; + this.path = GrailsResourceUtils.cleanPath(path) + this.classLoader = classLoader + this.clazz = clazz } /** * Return the path for this resource (as resource path within the class path). */ - public final String getPath() { - return path; + final String getPath() { + return path } /** * Return the ClassLoader that this resource will be obtained from. */ - public final ClassLoader getClassLoader() { - return classLoader == null ? clazz.getClassLoader() : classLoader; + final ClassLoader getClassLoader() { + return classLoader == null ? clazz.classLoader : classLoader } /** @@ -123,15 +119,15 @@ public final ClassLoader getClassLoader() { * @see java.lang.Class#getResource(String) */ @Override - public boolean exists() { - URL url; + boolean exists() { + URL url if (clazz == null) { - url = classLoader.getResource(path); + url = classLoader.getResource(path) } else { - url = clazz.getResource(path); + url = clazz.getResource(path) } - return url != null; + return url != null } /** @@ -139,19 +135,19 @@ public boolean exists() { * @see java.lang.ClassLoader#getResourceAsStream(String) * @see java.lang.Class#getResourceAsStream(String) */ - public InputStream getInputStream() throws IOException { - InputStream is; + InputStream getInputStream() throws IOException { + InputStream is if (clazz == null) { - is = classLoader.getResourceAsStream(path); + is = classLoader.getResourceAsStream(path) } else { - is = clazz.getResourceAsStream(path); + is = clazz.getResourceAsStream(path) } if (is == null) { throw new FileNotFoundException( - getDescription() + " cannot be opened because it does not exist"); + getDescription() + ' cannot be opened because it does not exist') } - return is; + return is } /** @@ -159,115 +155,115 @@ public InputStream getInputStream() throws IOException { * @see java.lang.ClassLoader#getResource(String) * @see java.lang.Class#getResource(String) */ - public URL getURL() throws IOException { - URL url; + URL getURL() throws IOException { + URL url if (clazz == null) { - url = classLoader.getResource(path); + url = classLoader.getResource(path) } else { - url = clazz.getResource(path); + url = clazz.getResource(path) } if (url == null) { throw new FileNotFoundException( - getDescription() + " cannot be resolved to URL because it does not exist"); + getDescription() + ' cannot be resolved to URL because it does not exist') } - return url; + return url } - public URI getURI() throws IOException { - return getFile().toURI(); + URI getURI() throws IOException { + return getFile().toURI() } /** * This implementation creates a ClassPathResource, applying the given path * relative to the path of the underlying resource of this descriptor. */ - public Resource createRelative(String relativePath) { - String pathToUse = GrailsResourceUtils.applyRelativePath(path, relativePath); - return new ClassPathResource(pathToUse, classLoader, clazz); + Resource createRelative(String relativePath) { + String pathToUse = GrailsResourceUtils.applyRelativePath(path, relativePath) + return new ClassPathResource(pathToUse, classLoader, clazz) } /** * This implementation returns the name of the file that this class path * resource refers to. */ - public String getFilename() { - return GrailsResourceUtils.getFilename(path); + String getFilename() { + return GrailsResourceUtils.getFilename(path) } /** * This implementation returns a description that includes the class path location. */ - public String getDescription() { - StringBuilder builder = new StringBuilder("class path resource ["); + String getDescription() { + StringBuilder builder = new StringBuilder('class path resource [') if (clazz != null) { - builder.append(GrailsResourceUtils.classPackageAsResourcePath(clazz)); - builder.append('/'); + builder << GrailsResourceUtils.classPackageAsResourcePath(clazz) + builder << '/' } - builder.append(path); - builder.append(']'); - return builder.toString(); + builder << path + builder << ']' + return builder.toString() } /** * This implementation compares the underlying class path locations. */ @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; + boolean equals(Object obj) { + if (this.is(obj)) { + return true } if (obj instanceof ClassPathResource) { - ClassPathResource otherRes = (ClassPathResource) obj; - return (path.equals(otherRes.path) && - nullSafeEquals(classLoader, otherRes.classLoader) && - nullSafeEquals(clazz, otherRes.clazz)); + ClassPathResource otherRes = (ClassPathResource) obj + return (path == otherRes.path && + nullSafeEquals(classLoader, otherRes.@classLoader) && + nullSafeEquals(clazz, otherRes.@clazz)) } - return false; + return false } private static boolean nullSafeEquals(Object o1, Object o2) { - if (o1 == o2) { - return true; + if (o1.is(o2)) { + return true } if (o1 == null || o2 == null) { - return false; + return false } if (o1.equals(o2)) { - return true; + return true } - if (o1.getClass().isArray() && o2.getClass().isArray()) { + if (o1.getClass().array && o2.getClass().array) { if (o1 instanceof Object[] && o2 instanceof Object[]) { - return Arrays.equals((Object[]) o1, (Object[]) o2); + return Arrays.equals((Object[]) o1, (Object[]) o2) } if (o1 instanceof boolean[] && o2 instanceof boolean[]) { - return Arrays.equals((boolean[]) o1, (boolean[]) o2); + return Arrays.equals((boolean[]) o1, (boolean[]) o2) } if (o1 instanceof byte[] && o2 instanceof byte[]) { - return Arrays.equals((byte[]) o1, (byte[]) o2); + return Arrays.equals((byte[]) o1, (byte[]) o2) } if (o1 instanceof char[] && o2 instanceof char[]) { - return Arrays.equals((char[]) o1, (char[]) o2); + return Arrays.equals((char[]) o1, (char[]) o2) } if (o1 instanceof double[] && o2 instanceof double[]) { - return Arrays.equals((double[]) o1, (double[]) o2); + return Arrays.equals((double[]) o1, (double[]) o2) } if (o1 instanceof float[] && o2 instanceof float[]) { - return Arrays.equals((float[]) o1, (float[]) o2); + return Arrays.equals((float[]) o1, (float[]) o2) } if (o1 instanceof int[] && o2 instanceof int[]) { - return Arrays.equals((int[]) o1, (int[]) o2); + return Arrays.equals((int[]) o1, (int[]) o2) } if (o1 instanceof long[] && o2 instanceof long[]) { - return Arrays.equals((long[]) o1, (long[]) o2); + return Arrays.equals((long[]) o1, (long[]) o2) } if (o1 instanceof short[] && o2 instanceof short[]) { - return Arrays.equals((short[]) o1, (short[]) o2); + return Arrays.equals((short[]) o1, (short[]) o2) } } - return false; + return false } /** @@ -275,7 +271,8 @@ private static boolean nullSafeEquals(Object o1, Object o2) { * class path location. */ @Override - public int hashCode() { - return path.hashCode(); + int hashCode() { + return path.hashCode() } + } diff --git a/grails-gradle/model/src/main/groovy/org/grails/io/support/DefaultResourceLoader.java b/grails-gradle/model/src/main/groovy/org/grails/io/support/DefaultResourceLoader.groovy similarity index 71% rename from grails-gradle/model/src/main/groovy/org/grails/io/support/DefaultResourceLoader.java rename to grails-gradle/model/src/main/groovy/org/grails/io/support/DefaultResourceLoader.groovy index c3b59f6d521..3115fda1d9d 100644 --- a/grails-gradle/model/src/main/groovy/org/grails/io/support/DefaultResourceLoader.java +++ b/grails-gradle/model/src/main/groovy/org/grails/io/support/DefaultResourceLoader.groovy @@ -16,10 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -package org.grails.io.support; +package org.grails.io.support -import java.net.MalformedURLException; -import java.net.URL; +import groovy.transform.CompileStatic /** * Default implementation of the ResourceLoader interface. @@ -31,9 +30,10 @@ * @author Juergen Hoeller * @since 10.03.2004 */ -public class DefaultResourceLoader implements ResourceLoader { +@CompileStatic +class DefaultResourceLoader implements ResourceLoader { - private ClassLoader classLoader; + private ClassLoader classLoader /** * Create a new DefaultResourceLoader. @@ -41,23 +41,23 @@ public class DefaultResourceLoader implements ResourceLoader { * at the time of this ResourceLoader's initialization. * @see java.lang.Thread#getContextClassLoader() */ - public DefaultResourceLoader() { - classLoader = getDefaultClassLoader(); + DefaultResourceLoader() { + classLoader = getDefaultClassLoader() } - public static ClassLoader getDefaultClassLoader() { - ClassLoader cl = null; + static ClassLoader getDefaultClassLoader() { + ClassLoader cl = null try { - cl = Thread.currentThread().getContextClassLoader(); + cl = Thread.currentThread().contextClassLoader } - catch (Throwable ex) { + catch (Throwable ignored) { // Cannot access thread context ClassLoader - falling back to system class loader... } if (cl == null) { // No thread context class loader -> use class loader of this class. - cl = DefaultResourceLoader.class.getClassLoader(); + cl = DefaultResourceLoader.classLoader } - return cl; + return cl } /** @@ -65,8 +65,8 @@ public static ClassLoader getDefaultClassLoader() { * @param classLoader the ClassLoader to load class path resources with, or null * for using the thread context class loader at the time of actual resource access */ - public DefaultResourceLoader(ClassLoader classLoader) { - this.classLoader = classLoader; + DefaultResourceLoader(ClassLoader classLoader) { + this.classLoader = classLoader } /** @@ -75,30 +75,30 @@ public DefaultResourceLoader(ClassLoader classLoader) { *

The default is that ClassLoader access will happen using the thread context * class loader at the time of this ResourceLoader's initialization. */ - public void setClassLoader(ClassLoader classLoader) { - this.classLoader = classLoader; + void setClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader } /** * Return the ClassLoader to load class path resources with. */ - public ClassLoader getClassLoader() { - return classLoader == null ? getDefaultClassLoader() : classLoader; + ClassLoader getClassLoader() { + return classLoader == null ? getDefaultClassLoader() : classLoader } - public Resource getResource(String location) { + Resource getResource(String location) { if (location.startsWith(CLASSPATH_URL_PREFIX)) { - return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); + return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()) } try { // Try to parse the location as a URL... - URL url = new URL(location); - return new UrlResource(url); + URL url = new URL(location) + return new UrlResource(url) } catch (MalformedURLException ex) { // No URL -> resolve as resource path. - return getResourceByPath(location); + return getResourceByPath(location) } } @@ -112,7 +112,7 @@ public Resource getResource(String location) { * @see ClassPathResource */ protected Resource getResourceByPath(String path) { - return new ClassPathContextResource(path, getClassLoader()); + return new ClassPathContextResource(path, getClassLoader()) } /** @@ -121,19 +121,20 @@ protected Resource getResourceByPath(String path) { */ private static class ClassPathContextResource extends ClassPathResource { - public ClassPathContextResource(String path, ClassLoader classLoader) { - super(path, classLoader); + ClassPathContextResource(String path, ClassLoader classLoader) { + super(path, classLoader) } - @SuppressWarnings("unused") - public String getPathWithinContext() { - return getPath(); + @SuppressWarnings('unused') + String getPathWithinContext() { + return path } @Override - public Resource createRelative(String relativePath) { - String pathToUse = GrailsResourceUtils.applyRelativePath(getPath(), relativePath); - return new ClassPathContextResource(pathToUse, getClassLoader()); + Resource createRelative(String relativePath) { + String pathToUse = GrailsResourceUtils.applyRelativePath(path, relativePath) + return new ClassPathContextResource(pathToUse, classLoader) } } + } diff --git a/grails-gradle/model/src/main/groovy/org/grails/io/support/FileSystemResource.java b/grails-gradle/model/src/main/groovy/org/grails/io/support/FileSystemResource.groovy similarity index 61% rename from grails-gradle/model/src/main/groovy/org/grails/io/support/FileSystemResource.java rename to grails-gradle/model/src/main/groovy/org/grails/io/support/FileSystemResource.groovy index f1243dafc0e..0a050ad542a 100644 --- a/grails-gradle/model/src/main/groovy/org/grails/io/support/FileSystemResource.java +++ b/grails-gradle/model/src/main/groovy/org/grails/io/support/FileSystemResource.groovy @@ -16,16 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -package org.grails.io.support; +package org.grails.io.support -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.URI; -import java.net.URL; +import groovy.transform.CompileStatic /** * Based on Spring FileSystemResource implementation. @@ -36,20 +29,21 @@ * @since 28.12.2003 * @see java.io.File */ -public class FileSystemResource implements Resource { +@CompileStatic +class FileSystemResource implements Resource { - private final File file; - private final String path; + private final File file + private final String path /** * Create a new FileSystemResource from a File handle. * * @param file a File handle */ - public FileSystemResource(File file) { - assertNotNull(file, "File must not be null"); - this.file = file; - path = GrailsResourceUtils.cleanPath(file.getPath()); + FileSystemResource(File file) { + assertNotNull(file, 'File must not be null') + this.file = file + path = GrailsResourceUtils.cleanPath(file.path) } /** @@ -57,25 +51,25 @@ public FileSystemResource(File file) { * * @param path a file path */ - public FileSystemResource(String path) { - assertNotNull(path, "Path must not be null"); - file = new File(path); - this.path = GrailsResourceUtils.cleanPath(path); + FileSystemResource(String path) { + assertNotNull(path, 'Path must not be null') + file = new File(path) + this.path = GrailsResourceUtils.cleanPath(path) } /** * Return the file path for this resource. */ - public final String getPath() { - return path; + final String getPath() { + return path } /** * This implementation returns whether the underlying file exists. * @see java.io.File#exists() */ - public boolean exists() { - return file.exists(); + boolean exists() { + return file.exists() } /** @@ -84,58 +78,58 @@ public boolean exists() { * @see java.io.File#canRead() * @see java.io.File#isDirectory() */ - public boolean isReadable() { - return file.canRead() && !file.isDirectory(); + boolean isReadable() { + return file.canRead() && !file.isDirectory() } /** * This implementation opens a FileInputStream for the underlying file. * @see java.io.FileInputStream */ - public InputStream getInputStream() throws IOException { - return new FileInputStream(file); + InputStream getInputStream() throws IOException { + return new FileInputStream(file) } /** * This implementation returns a URL for the underlying file. * @see java.io.File#toURI() */ - public URL getURL() throws IOException { - return file.toURI().toURL(); + URL getURL() throws IOException { + return file.toURI().toURL() } /** * This implementation returns a URI for the underlying file. * @see java.io.File#toURI() */ - public URI getURI() throws IOException { - return file.toURI(); + URI getURI() throws IOException { + return file.toURI() } /** * This implementation returns the underlying File reference. */ - public File getFile() { - return file; + File getFile() { + return file } /** * This implementation returns the underlying File's length. */ - public long contentLength() throws IOException { - return file.length(); + long contentLength() throws IOException { + return file.length() } - public long lastModified() throws IOException { - return file.lastModified(); + long lastModified() throws IOException { + return file.lastModified() } /** * This implementation returns the name of the file. * @see java.io.File#getName() */ - public String getFilename() { - return file.getName(); + String getFilename() { + return file.name } /** @@ -143,17 +137,17 @@ public String getFilename() { * path of the file. * @see java.io.File#getAbsolutePath() */ - public String getDescription() { - return "file [" + file.getAbsolutePath() + "]"; + String getDescription() { + return "file [${file.absolutePath}]" } /** * This implementation creates a FileSystemResource, applying the given path * relative to the path of the underlying file of this resource descriptor. */ - public Resource createRelative(String relativePath) { - String pathToUse = GrailsResourceUtils.applyRelativePath(path, relativePath); - return new FileSystemResource(pathToUse); + Resource createRelative(String relativePath) { + String pathToUse = GrailsResourceUtils.applyRelativePath(path, relativePath) + return new FileSystemResource(pathToUse) } // implementation of WritableResource @@ -164,43 +158,44 @@ public Resource createRelative(String relativePath) { * @see java.io.File#canWrite() * @see java.io.File#isDirectory() */ - public boolean isWritable() { - return file.canWrite() && !file.isDirectory(); + boolean isWritable() { + return file.canWrite() && !file.isDirectory() } /** * This implementation opens a FileOutputStream for the underlying file. * @see java.io.FileOutputStream */ - public OutputStream getOutputStream() throws IOException { - return new FileOutputStream(file); + OutputStream getOutputStream() throws IOException { + return new FileOutputStream(file) } /** * This implementation compares the underlying File references. */ @Override - public boolean equals(Object obj) { - return (obj == this || - (obj instanceof FileSystemResource && path.equals(((FileSystemResource) obj).path))); + boolean equals(Object obj) { + return (this.is(obj) || + (obj instanceof FileSystemResource && path == ((FileSystemResource) obj).path)) } /** * This implementation returns the hash code of the underlying File reference. */ @Override - public int hashCode() { - return path.hashCode(); + int hashCode() { + return path.hashCode() } protected void assertNotNull(Object object, String message) { if (object == null) { - throw new IllegalArgumentException(message); + throw new IllegalArgumentException(message) } } @Override - public String toString() { - return file.toString(); + String toString() { + return file.toString() } + } diff --git a/grails-gradle/model/src/main/groovy/org/grails/io/support/GrailsResourceUtils.java b/grails-gradle/model/src/main/groovy/org/grails/io/support/GrailsResourceUtils.groovy similarity index 58% rename from grails-gradle/model/src/main/groovy/org/grails/io/support/GrailsResourceUtils.java rename to grails-gradle/model/src/main/groovy/org/grails/io/support/GrailsResourceUtils.groovy index 2861fea39e1..ce7b9ec9f82 100644 --- a/grails-gradle/model/src/main/groovy/org/grails/io/support/GrailsResourceUtils.java +++ b/grails-gradle/model/src/main/groovy/org/grails/io/support/GrailsResourceUtils.groovy @@ -16,31 +16,15 @@ * specific language governing permissions and limitations * under the License. */ -package org.grails.io.support; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLConnection; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import groovy.lang.Closure; -import groovy.util.ConfigObject; -import org.codehaus.groovy.runtime.DefaultGroovyMethods; - -import grails.util.BuildSettings; +package org.grails.io.support + +import java.util.regex.Matcher +import java.util.regex.Pattern + +import groovy.transform.CompileStatic +import org.codehaus.groovy.runtime.DefaultGroovyMethods + +import grails.util.BuildSettings /** * Utility methods for resource handling / figuring out class names. @@ -49,171 +33,181 @@ * @author Juergen Hoeller * @since 2.0 */ -public class GrailsResourceUtils { +@CompileStatic +class GrailsResourceUtils { - public static final String CLASS_EXTENSION = ".class"; + public static final String CLASS_EXTENSION = '.class' - private static final String WINDOWS_FOLDER_SEPARATOR = "\\"; + private static final String WINDOWS_FOLDER_SEPARATOR = '\\' - private static final String TOP_PATH = ".."; + private static final String TOP_PATH = '..' - private static final String CURRENT_PATH = "."; + private static final String CURRENT_PATH = '.' - private static final String FOLDER_SEPARATOR = "/"; - public static final String JAR_URL_SEPARATOR = "!/"; + private static final String FOLDER_SEPARATOR = '/' + public static final String JAR_URL_SEPARATOR = '!/' /** Pseudo URL prefix for loading from the class path: "classpath:" */ - public static final String CLASSPATH_URL_PREFIX = "classpath:"; + public static final String CLASSPATH_URL_PREFIX = 'classpath:' /** URL prefix for loading from the file system: "file:" */ - public static final String FILE_URL_PREFIX = "file:"; + public static final String FILE_URL_PREFIX = 'file:' /** URL protocol for a file in the file system: "file" */ - public static final String URL_PROTOCOL_FILE = "file"; + public static final String URL_PROTOCOL_FILE = 'file' /** URL protocol for an entry from a jar file: "jar" */ - public static final String URL_PROTOCOL_JAR = "jar"; + public static final String URL_PROTOCOL_JAR = 'jar' /** URL protocol for an entry from a zip file: "zip" */ - public static final String URL_PROTOCOL_ZIP = "zip"; + public static final String URL_PROTOCOL_ZIP = 'zip' /** URL protocol for an entry from a JBoss jar file: "vfszip" */ - public static final String URL_PROTOCOL_VFSZIP = "vfszip"; + public static final String URL_PROTOCOL_VFSZIP = 'vfszip' /** URL protocol for a JBoss VFS resource: "vfs" */ - public static final String URL_PROTOCOL_VFS = "vfs"; + public static final String URL_PROTOCOL_VFS = 'vfs' /** URL protocol for an entry from a WebSphere jar file: "wsjar" */ - public static final String URL_PROTOCOL_WSJAR = "wsjar"; + public static final String URL_PROTOCOL_WSJAR = 'wsjar' /** URL protocol for an entry from an OC4J jar file: "code-source" */ - public static final String URL_PROTOCOL_CODE_SOURCE = "code-source"; + public static final String URL_PROTOCOL_CODE_SOURCE = 'code-source' /** * The relative path to the WEB-INF directory */ - public static final String WEB_INF = "/WEB-INF"; + public static final String WEB_INF = '/WEB-INF' /** * The name of the Grails application directory */ - public static final String GRAILS_APP_DIR = "grails-app"; + public static final String GRAILS_APP_DIR = 'grails-app' /** * The name of the Web app dir within Grails */ - public static final String WEB_APP_DIR = "web-app"; + public static final String WEB_APP_DIR = 'web-app' /** * The path to the views directory */ - public static final String VIEWS_DIR_PATH = GRAILS_APP_DIR + "/views/"; + public static final String VIEWS_DIR_PATH = GRAILS_APP_DIR + '/views/' /** * The path to the views directory without a trailing slash */ - public static final String VIEWS_DIR_PATH_NO_SLASH = GRAILS_APP_DIR + "/views"; + public static final String VIEWS_DIR_PATH_NO_SLASH = GRAILS_APP_DIR + '/views' - public static final String DOMAIN_DIR_PATH = GRAILS_APP_DIR + "/domain/"; + public static final String DOMAIN_DIR_PATH = GRAILS_APP_DIR + '/domain/' - public static final String REGEX_FILE_SEPARATOR = "[\\\\/]"; // backslashes need escaping in regexes + public static final String REGEX_FILE_SEPARATOR = '[\\\\/]' // backslashes need escaping in regexes /* Domain path is always matched against the normalized File representation of an URL and can therefore work with slashes as separators. */ - public static Pattern DOMAIN_PATH_PATTERN = Pattern.compile(".+" + REGEX_FILE_SEPARATOR + GRAILS_APP_DIR + REGEX_FILE_SEPARATOR + "domain" + REGEX_FILE_SEPARATOR + "(.+)\\.(groovy|java)"); + public static Pattern DOMAIN_PATH_PATTERN = Pattern.compile('.+' + REGEX_FILE_SEPARATOR + GRAILS_APP_DIR + REGEX_FILE_SEPARATOR + 'domain' + REGEX_FILE_SEPARATOR + '(.+)\\.(groovy|java)') /* This pattern will match any resource within a given directory inside grails-app */ - public static Pattern RESOURCE_PATH_PATTERN = Pattern.compile(".+?" + REGEX_FILE_SEPARATOR + GRAILS_APP_DIR + REGEX_FILE_SEPARATOR + "(.+?)" + REGEX_FILE_SEPARATOR + "(.+?\\.(groovy|java))"); + public static Pattern RESOURCE_PATH_PATTERN = Pattern.compile('.+?' + REGEX_FILE_SEPARATOR + GRAILS_APP_DIR + REGEX_FILE_SEPARATOR + '(.+?)' + REGEX_FILE_SEPARATOR + '(.+?\\.(groovy|java))') - public static Pattern SPRING_SCRIPTS_PATH_PATTERN = Pattern.compile(".+?" + REGEX_FILE_SEPARATOR + GRAILS_APP_DIR + REGEX_FILE_SEPARATOR + "conf" + REGEX_FILE_SEPARATOR + "spring" + REGEX_FILE_SEPARATOR + "(.+?\\.groovy)"); + public static Pattern SPRING_SCRIPTS_PATH_PATTERN = Pattern.compile('.+?' + REGEX_FILE_SEPARATOR + GRAILS_APP_DIR + REGEX_FILE_SEPARATOR + 'conf' + REGEX_FILE_SEPARATOR + 'spring' + REGEX_FILE_SEPARATOR + '(.+?\\.groovy)') - public static Pattern[] COMPILER_ROOT_PATTERNS = { + public static Pattern[] COMPILER_ROOT_PATTERNS = [ SPRING_SCRIPTS_PATH_PATTERN, RESOURCE_PATH_PATTERN - }; + ] as Pattern[] /* Resources are resolved against the platform specific path and must therefore obey the specific File.separator. */ - public static final Pattern GRAILS_RESOURCE_PATTERN_FIRST_MATCH; - public static final Pattern GRAILS_RESOURCE_PATTERN_SECOND_MATCH; - public static final Pattern GRAILS_RESOURCE_PATTERN_THIRD_MATCH; - public static final Pattern GRAILS_RESOURCE_PATTERN_FOURTH_MATCH; - public static final Pattern GRAILS_RESOURCE_PATTERN_FIFTH_MATCH; - public static final Pattern GRAILS_RESOURCE_PATTERN_SIXTH_MATCH; - public static final Pattern GRAILS_RESOURCE_PATTERN_SEVENTH_MATCH; - public static final Pattern GRAILS_RESOURCE_PATTERN_EIGHTH_MATCH; - public static final Pattern GRAILS_RESOURCE_PATTERN_NINTH_MATCH; - public static final Pattern GRAILS_RESOURCE_PATTERN_TENTH_MATCH; - public static final Pattern GRAILS_RESOURCE_PATTERN_ELEVENTH_MATCH; + public static final Pattern GRAILS_RESOURCE_PATTERN_FIRST_MATCH + public static final Pattern GRAILS_RESOURCE_PATTERN_SECOND_MATCH + public static final Pattern GRAILS_RESOURCE_PATTERN_THIRD_MATCH + public static final Pattern GRAILS_RESOURCE_PATTERN_FOURTH_MATCH + public static final Pattern GRAILS_RESOURCE_PATTERN_FIFTH_MATCH + public static final Pattern GRAILS_RESOURCE_PATTERN_SIXTH_MATCH + public static final Pattern GRAILS_RESOURCE_PATTERN_SEVENTH_MATCH + public static final Pattern GRAILS_RESOURCE_PATTERN_EIGHTH_MATCH + public static final Pattern GRAILS_RESOURCE_PATTERN_NINTH_MATCH + public static final Pattern GRAILS_RESOURCE_PATTERN_TENTH_MATCH + public static final Pattern GRAILS_RESOURCE_PATTERN_ELEVENTH_MATCH static { - String fs = REGEX_FILE_SEPARATOR; - - GRAILS_RESOURCE_PATTERN_FIRST_MATCH = Pattern.compile(createGrailsResourcePattern(fs, GRAILS_APP_DIR + fs + "conf" + fs + "spring")); - GRAILS_RESOURCE_PATTERN_THIRD_MATCH = Pattern.compile(createGrailsResourcePattern(fs, GRAILS_APP_DIR + fs + "[\\w-]+")); - GRAILS_RESOURCE_PATTERN_SEVENTH_MATCH = Pattern.compile(createGrailsResourcePattern(fs, "src" + fs + "main" + fs + "java")); - GRAILS_RESOURCE_PATTERN_EIGHTH_MATCH = Pattern.compile(createGrailsResourcePattern(fs, "src" + fs + "main" + fs + "groovy")); - - GRAILS_RESOURCE_PATTERN_NINTH_MATCH = Pattern.compile(createGrailsResourcePattern(fs, "src" + fs + "test" + fs + "groovy")); - GRAILS_RESOURCE_PATTERN_TENTH_MATCH = Pattern.compile(createGrailsResourcePattern(fs, "src" + fs + "test" + fs + "java")); - GRAILS_RESOURCE_PATTERN_ELEVENTH_MATCH = Pattern.compile(createGrailsResourcePattern(fs, "src" + fs + "test" + fs + "functional")); - - GRAILS_RESOURCE_PATTERN_FIFTH_MATCH = Pattern.compile(createGrailsResourcePattern(fs, "grails-tests")); - fs = "/"; - GRAILS_RESOURCE_PATTERN_SECOND_MATCH = Pattern.compile(createGrailsResourcePattern(fs, GRAILS_APP_DIR + fs + "conf" + fs + "spring")); - GRAILS_RESOURCE_PATTERN_FOURTH_MATCH = Pattern.compile(createGrailsResourcePattern(fs, GRAILS_APP_DIR + fs + "[\\w-]+")); - GRAILS_RESOURCE_PATTERN_SIXTH_MATCH = Pattern.compile(createGrailsResourcePattern(fs, "grails-tests")); - } - - public static final Pattern[] patterns = new Pattern[]{ - GRAILS_RESOURCE_PATTERN_FIRST_MATCH, - GRAILS_RESOURCE_PATTERN_THIRD_MATCH, - GRAILS_RESOURCE_PATTERN_SEVENTH_MATCH, - GRAILS_RESOURCE_PATTERN_EIGHTH_MATCH, - GRAILS_RESOURCE_PATTERN_FOURTH_MATCH, - GRAILS_RESOURCE_PATTERN_FIFTH_MATCH, - GRAILS_RESOURCE_PATTERN_SIXTH_MATCH, - GRAILS_RESOURCE_PATTERN_NINTH_MATCH, - GRAILS_RESOURCE_PATTERN_TENTH_MATCH, - GRAILS_RESOURCE_PATTERN_ELEVENTH_MATCH - }; - - public static final Pattern[] grailsAppResourcePatterns = new Pattern[]{ - GRAILS_RESOURCE_PATTERN_FIRST_MATCH, - GRAILS_RESOURCE_PATTERN_THIRD_MATCH, - GRAILS_RESOURCE_PATTERN_FOURTH_MATCH, - GRAILS_RESOURCE_PATTERN_FIFTH_MATCH, - GRAILS_RESOURCE_PATTERN_SIXTH_MATCH, - GRAILS_RESOURCE_PATTERN_ELEVENTH_MATCH - }; - - private static Map KNOWN_PATHS = new LinkedHashMap<>() { + String fs = REGEX_FILE_SEPARATOR + + GRAILS_RESOURCE_PATTERN_FIRST_MATCH = Pattern.compile(createGrailsResourcePattern(fs, GRAILS_APP_DIR + fs + 'conf' + fs + 'spring')) + GRAILS_RESOURCE_PATTERN_THIRD_MATCH = Pattern.compile(createGrailsResourcePattern(fs, GRAILS_APP_DIR + fs + '[\\w-]+')) + GRAILS_RESOURCE_PATTERN_SEVENTH_MATCH = Pattern.compile(createGrailsResourcePattern(fs, 'src' + fs + 'main' + fs + 'java')) + GRAILS_RESOURCE_PATTERN_EIGHTH_MATCH = Pattern.compile(createGrailsResourcePattern(fs, 'src' + fs + 'main' + fs + 'groovy')) + + GRAILS_RESOURCE_PATTERN_NINTH_MATCH = Pattern.compile(createGrailsResourcePattern(fs, 'src' + fs + 'test' + fs + 'groovy')) + GRAILS_RESOURCE_PATTERN_TENTH_MATCH = Pattern.compile(createGrailsResourcePattern(fs, 'src' + fs + 'test' + fs + 'java')) + GRAILS_RESOURCE_PATTERN_ELEVENTH_MATCH = Pattern.compile(createGrailsResourcePattern(fs, 'src' + fs + 'test' + fs + 'functional')) + + GRAILS_RESOURCE_PATTERN_FIFTH_MATCH = Pattern.compile(createGrailsResourcePattern(fs, 'grails-tests')) + fs = '/' + GRAILS_RESOURCE_PATTERN_SECOND_MATCH = Pattern.compile(createGrailsResourcePattern(fs, GRAILS_APP_DIR + fs + 'conf' + fs + 'spring')) + GRAILS_RESOURCE_PATTERN_FOURTH_MATCH = Pattern.compile(createGrailsResourcePattern(fs, GRAILS_APP_DIR + fs + '[\\w-]+')) + GRAILS_RESOURCE_PATTERN_SIXTH_MATCH = Pattern.compile(createGrailsResourcePattern(fs, 'grails-tests')) + + // Assigned here, not as separate field initializers below: Groovy does not preserve + // Java's textual static-initialization order across a static {} block and later field + // initializers, so referencing these fields from a field initializer placed after this + // block would silently capture null (confirmed empirically) rather than the values just + // assigned above. + patterns = new Pattern[]{ + GRAILS_RESOURCE_PATTERN_FIRST_MATCH, + GRAILS_RESOURCE_PATTERN_THIRD_MATCH, + GRAILS_RESOURCE_PATTERN_SEVENTH_MATCH, + GRAILS_RESOURCE_PATTERN_EIGHTH_MATCH, + GRAILS_RESOURCE_PATTERN_FOURTH_MATCH, + GRAILS_RESOURCE_PATTERN_FIFTH_MATCH, + GRAILS_RESOURCE_PATTERN_SIXTH_MATCH, + GRAILS_RESOURCE_PATTERN_NINTH_MATCH, + GRAILS_RESOURCE_PATTERN_TENTH_MATCH, + GRAILS_RESOURCE_PATTERN_ELEVENTH_MATCH + } + + grailsAppResourcePatterns = new Pattern[]{ + GRAILS_RESOURCE_PATTERN_FIRST_MATCH, + GRAILS_RESOURCE_PATTERN_THIRD_MATCH, + GRAILS_RESOURCE_PATTERN_FOURTH_MATCH, + GRAILS_RESOURCE_PATTERN_FIFTH_MATCH, + GRAILS_RESOURCE_PATTERN_SIXTH_MATCH, + GRAILS_RESOURCE_PATTERN_ELEVENTH_MATCH + } + } + + public static final Pattern[] patterns + + public static final Pattern[] grailsAppResourcePatterns + + private static Map KNOWN_PATHS = new LinkedHashMap() { @Override protected boolean removeEldestEntry(Map.Entry eldest) { - return this.size() > 100; + return this.size() > 100 } - }; + } private static Map KNOWN_DOMAIN_CLASSES = DefaultGroovyMethods.withDefault(new LinkedHashMap() { @Override protected boolean removeEldestEntry(Map.Entry eldest) { - return this.size() > 100; + return this.size() > 100 } - }, new Closure(GrailsResourceUtils.class) { + }, new Closure(GrailsResourceUtils) { @Override - public Object call(Object... args) { - String path = args[0].toString(); - return DOMAIN_PATH_PATTERN.matcher(path).find(); + Object call(Object... args) { + String path = args[0].toString() + return DOMAIN_PATH_PATTERN.matcher(path).find() } - }); + }) private static String createGrailsResourcePattern(String separator, String base) { - return ".+" + separator + base + separator + "(.+)\\.(groovy|java)$"; + return '.+' + separator + base + separator + '(.+)\\.(groovy|java)\$' } /** @@ -223,9 +217,9 @@ private static String createGrailsResourcePattern(String separator, String base) * @return true if it is a domain class */ - public static boolean isDomainClass(URL url) { - if (url == null) return false; - return KNOWN_DOMAIN_CLASSES.get(url.getFile()); + static boolean isDomainClass(URL url) { + if (url == null) return false + return KNOWN_DOMAIN_CLASSES[url.file] } /** @@ -234,12 +228,12 @@ public static boolean isDomainClass(URL url) { * @param path the file path (may be null) * @return the extracted filename, or null if none */ - public static String getFilename(String path) { + static String getFilename(String path) { if (path == null) { - return null; + return null } - int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR); - return (separatorIndex != -1 ? path.substring(separatorIndex + 1) : path); + int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR) + return (separatorIndex != -1 ? path.substring(separatorIndex + 1) : path) } /** @@ -256,21 +250,21 @@ public static String getFilename(String path) { * @see ClassLoader#getResource * @see Class#getResource */ - public static String classPackageAsResourcePath(Class clazz) { + static String classPackageAsResourcePath(Class clazz) { if (clazz == null) { - return ""; + return '' } - String className = clazz.getName(); - int packageEndIndex = className.lastIndexOf('.'); + String className = clazz.name + int packageEndIndex = className.lastIndexOf('.') if (packageEndIndex == -1) { - return ""; + return '' } - String packageName = className.substring(0, packageEndIndex); - return packageName.replace('.', '/'); + String packageName = className.substring(0, packageEndIndex) + return packageName.replace('.', '/') } - public static void useCachesIfNecessary(URLConnection con) { - con.setUseCaches(con.getClass().getName().startsWith("JNLP")); + static void useCachesIfNecessary(URLConnection con) { + con.useCaches = con.getClass().name.startsWith('JNLP') } /** @@ -279,12 +273,12 @@ public static void useCachesIfNecessary(URLConnection con) { * @param resource The Spring Resource * @return The class name or null if the resource is not a Grails class */ - public static String getClassName(Resource resource) { + static String getClassName(Resource resource) { try { - return getClassName(resource.getFile().getAbsolutePath()); + return getClassName(resource.file.absolutePath) } catch (IOException e) { - return null; + return null } } @@ -294,14 +288,14 @@ public static String getClassName(Resource resource) { * @param path The path to check * @return The class name or null if it doesn't exist */ - public static String getClassName(String path) { - for (Pattern pattern : patterns) { - Matcher m = pattern.matcher(path); + static String getClassName(String path) { + for (Pattern pattern in patterns) { + Matcher m = pattern.matcher(path) if (m.find()) { - return m.group(1).replaceAll("[/\\\\]", "."); + return m.group(1).replaceAll('[/\\\\]', '.') } } - return null; + return null } /** @@ -310,14 +304,14 @@ public static String getClassName(String path) { * @param path The path to check * @return The class name or null if it doesn't exist */ - public static String getClassNameForClassFile(String rootDir, String path) { - path = path.replace("/", "."); - path = path.replace('\\', '.'); - path = path.substring(0, path.length() - CLASS_EXTENSION.length()); + static String getClassNameForClassFile(String rootDir, String path) { + path = path.replace('/', '.') + path = path.replace('\\', '.') + path = path.substring(0, path.length() - CLASS_EXTENSION.length()) if (rootDir != null) { - path = path.substring(rootDir.length()); + path = path.substring(rootDir.length()) } - return path; + return path } /** @@ -330,18 +324,18 @@ public static String getClassNameForClassFile(String rootDir, String path) { * @throws java.io.FileNotFoundException if the URL cannot be resolved to * a file in the file system */ - public static File getFile(URL resourceUrl, String description) throws FileNotFoundException { - if (!URL_PROTOCOL_FILE.equals(resourceUrl.getProtocol())) { + static File getFile(URL resourceUrl, String description) throws FileNotFoundException { + if (URL_PROTOCOL_FILE != resourceUrl.protocol) { throw new FileNotFoundException( - description + " cannot be resolved to absolute file path " + - "because it does not reside in the file system: " + resourceUrl); + description + ' cannot be resolved to absolute file path ' + + 'because it does not reside in the file system: ' + resourceUrl) } try { - return new File(toURI(resourceUrl).getSchemeSpecificPart()); + return new File(toURI(resourceUrl).schemeSpecificPart) } catch (URISyntaxException ex) { // Fallback for URLs that are not valid URIs (should hardly ever happen). - return new File(resourceUrl.getFile()); + return new File(resourceUrl.file) } } @@ -354,12 +348,12 @@ public static File getFile(URL resourceUrl, String description) throws FileNotFo * @param url the URL to check * @return whether the URL has been identified as a JAR URL */ - public static boolean isJarURL(URL url) { - String protocol = url.getProtocol(); - return (URL_PROTOCOL_JAR.equals(protocol) || - URL_PROTOCOL_ZIP.equals(protocol) || - URL_PROTOCOL_WSJAR.equals(protocol) || - (URL_PROTOCOL_CODE_SOURCE.equals(protocol) && url.getPath().contains(JAR_URL_SEPARATOR))); + static boolean isJarURL(URL url) { + String protocol = url.protocol + return (URL_PROTOCOL_JAR == protocol || + URL_PROTOCOL_ZIP == protocol || + URL_PROTOCOL_WSJAR == protocol || + (URL_PROTOCOL_CODE_SOURCE == protocol && url.path.contains(JAR_URL_SEPARATOR))) } /** @@ -372,13 +366,13 @@ public static boolean isJarURL(URL url) { * @throws FileNotFoundException if the URL cannot be resolved to * a file in the file system */ - public static File getFile(URI resourceUri, String description) throws FileNotFoundException { - if (!URL_PROTOCOL_FILE.equals(resourceUri.getScheme())) { + static File getFile(URI resourceUri, String description) throws FileNotFoundException { + if (URL_PROTOCOL_FILE != resourceUri.scheme) { throw new FileNotFoundException( - description + " cannot be resolved to absolute file path " + - "because it does not reside in the file system: " + resourceUri); + description + ' cannot be resolved to absolute file path ' + + 'because it does not reside in the file system: ' + resourceUri) } - return new File(resourceUri.getSchemeSpecificPart()); + return new File(resourceUri.schemeSpecificPart) } /** @@ -389,8 +383,8 @@ public static File getFile(URI resourceUri, String description) throws FileNotFo * @throws FileNotFoundException if the URL cannot be resolved to * a file in the file system */ - public static File getFile(URI resourceUri) throws FileNotFoundException { - return getFile(resourceUri, "URI"); + static File getFile(URI resourceUri) throws FileNotFoundException { + return getFile(resourceUri, 'URI') } /** @@ -403,8 +397,8 @@ public static File getFile(URI resourceUri) throws FileNotFoundException { * @throws URISyntaxException if the URL wasn't a valid URI * @see java.net.URL#toURI() */ - public static URI toURI(URL url) throws URISyntaxException { - return toURI(url.toString()); + static URI toURI(URL url) throws URISyntaxException { + return toURI(url.toString()) } /** @@ -413,9 +407,9 @@ public static URI toURI(URL url) throws URISyntaxException { * @param url the URL to check * @return whether the URL has been identified as a file system URL */ - public static boolean isFileURL(URL url) { - String protocol = url.getProtocol(); - return (URL_PROTOCOL_FILE.equals(protocol) || protocol.startsWith(URL_PROTOCOL_VFS)); + static boolean isFileURL(URL url) { + String protocol = url.protocol + return (URL_PROTOCOL_FILE == protocol || protocol.startsWith(URL_PROTOCOL_VFS)) } /** @@ -426,16 +420,16 @@ public static boolean isFileURL(URL url) { * (relative to the full file path above) * @return the full file path that results from applying the relative path */ - public static String applyRelativePath(String path, String relativePath) { - int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR); + static String applyRelativePath(String path, String relativePath) { + int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR) if (separatorIndex != -1) { - String newPath = path.substring(0, separatorIndex); + String newPath = path.substring(0, separatorIndex) if (!relativePath.startsWith(FOLDER_SEPARATOR)) { - newPath += FOLDER_SEPARATOR; + newPath += FOLDER_SEPARATOR } - return newPath + relativePath; + return newPath + relativePath } - return relativePath; + return relativePath } /** @@ -446,77 +440,77 @@ public static String applyRelativePath(String path, String relativePath) { * @param path the original path * @return the normalized path */ - public static String cleanPath(String path) { + static String cleanPath(String path) { if (path == null) { - return null; + return null } - String pathToUse = replace(path, WINDOWS_FOLDER_SEPARATOR, FOLDER_SEPARATOR); + String pathToUse = replace(path, WINDOWS_FOLDER_SEPARATOR, FOLDER_SEPARATOR) // Strip prefix from path to analyze, to not treat it as part of the // first path element. This is necessary to correctly parse paths like // "file:core/../core/io/Resource.class", where the ".." should just // strip the first "core" directory while keeping the "file:" prefix. - int prefixIndex = pathToUse.indexOf(":"); - String prefix = ""; + int prefixIndex = pathToUse.indexOf(':') + String prefix = '' if (prefixIndex != -1) { - prefix = pathToUse.substring(0, prefixIndex + 1); - pathToUse = pathToUse.substring(prefixIndex + 1); + prefix = pathToUse.substring(0, prefixIndex + 1) + pathToUse = pathToUse.substring(prefixIndex + 1) } if (pathToUse.startsWith(FOLDER_SEPARATOR)) { - prefix = prefix + FOLDER_SEPARATOR; - pathToUse = pathToUse.substring(1); + prefix = prefix + FOLDER_SEPARATOR + pathToUse = pathToUse.substring(1) } - String[] pathArray = delimitedListToStringArray(pathToUse, FOLDER_SEPARATOR); - List pathElements = new LinkedList<>(); - int tops = 0; + String[] pathArray = delimitedListToStringArray(pathToUse, FOLDER_SEPARATOR) + List pathElements = new LinkedList<>() + int tops = 0 for (int i = pathArray.length - 1; i >= 0; i--) { - String element = pathArray[i]; - if (CURRENT_PATH.equals(element)) { + String element = pathArray[i] + if (CURRENT_PATH == element) { // Points to current directory - drop it. } - else if (TOP_PATH.equals(element)) { + else if (TOP_PATH == element) { // Registering top path found. - tops++; + tops++ } else { if (tops > 0) { // Merging path element with element corresponding to top path. - tops--; + tops-- } else { // Normal path element found. - pathElements.add(0, element); + pathElements.add(0, element) } } } // Remaining top paths need to be retained. for (int i = 0; i < tops; i++) { - pathElements.add(0, TOP_PATH); + pathElements.add(0, TOP_PATH) } - return prefix + collectionToDelimitedString(pathElements, FOLDER_SEPARATOR); + return prefix + collectionToDelimitedString(pathElements, FOLDER_SEPARATOR) } private static String collectionToDelimitedString(Collection coll, String delim) { - return collectionToDelimitedString(coll, delim, "", ""); + return collectionToDelimitedString(coll, delim, '', '') } private static String collectionToDelimitedString(Collection coll, String delim, String prefix, String suffix) { if (coll == null || coll.isEmpty()) { - return ""; + return '' } - StringBuilder sb = new StringBuilder(); - Iterator it = coll.iterator(); + StringBuilder sb = new StringBuilder() + Iterator it = coll.iterator() while (it.hasNext()) { - sb.append(prefix).append(it.next()).append(suffix); + sb << prefix << it.next() << suffix if (it.hasNext()) { - sb.append(delim); + sb << delim } } - return sb.toString(); + return sb.toString() } /** @@ -530,7 +524,7 @@ private static String collectionToDelimitedString(Collection coll, String del * @return an array of the tokens in the list */ private static String[] delimitedListToStringArray(String str, String delimiter) { - return delimitedListToStringArray(str, delimiter, null); + return delimitedListToStringArray(str, delimiter, null) } /** @@ -547,51 +541,51 @@ private static String[] delimitedListToStringArray(String str, String delimiter) */ private static String[] delimitedListToStringArray(String str, String delimiter, String charsToDelete) { if (str == null) { - return new String[0]; + return new String[0] } if (delimiter == null) { - return new String[] {str}; + return [str] as String[] } - List result = new ArrayList<>(); - if ("".equals(delimiter)) { + List result = new ArrayList<>() + if (delimiter == '') { for (int i = 0; i < str.length(); i++) { - result.add(deleteAny(str.substring(i, i + 1), charsToDelete)); + result << deleteAny(str.substring(i, i + 1), charsToDelete) } } else { - int pos = 0; - int delPos; + int pos = 0 + int delPos while ((delPos = str.indexOf(delimiter, pos)) != -1) { - result.add(deleteAny(str.substring(pos, delPos), charsToDelete)); - pos = delPos + delimiter.length(); + result << deleteAny(str.substring(pos, delPos), charsToDelete) + pos = delPos + delimiter.length() } - if (str.length() > 0 && pos <= str.length()) { + if (!str.isEmpty() && pos <= str.length()) { // Add rest of String, but not in case of empty input. - result.add(deleteAny(str.substring(pos), charsToDelete)); + result << deleteAny(str.substring(pos), charsToDelete) } } - return toStringArray(result); + return toStringArray(result) } private static String[] toStringArray(Collection collection) { if (collection == null) { - return null; + return null } - return collection.toArray(new String[collection.size()]); + return collection.toArray(new String[collection.size()]) } private static String deleteAny(String inString, String charsToDelete) { if (!hasLength(inString) || !hasLength(charsToDelete)) { - return inString; + return inString } - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder() for (int i = 0; i < inString.length(); i++) { - char c = inString.charAt(i); - if (charsToDelete.indexOf(c) == -1) { - sb.append(c); + char c = inString.charAt(i) + if (charsToDelete.indexOf((int) c) == -1) { + sb << c } } - return sb.toString(); + return sb.toString() } /** @@ -604,26 +598,26 @@ private static String deleteAny(String inString, String charsToDelete) { */ private static String replace(String inString, String oldPattern, String newPattern) { if (!hasLength(inString) || !hasLength(oldPattern) || newPattern == null) { - return inString; + return inString } - StringBuilder sb = new StringBuilder(); - int pos = 0; // our position in the old string - int index = inString.indexOf(oldPattern); + StringBuilder sb = new StringBuilder() + int pos = 0 // our position in the old string + int index = inString.indexOf(oldPattern) // the index of an occurrence we've found, or -1 - int patLen = oldPattern.length(); + int patLen = oldPattern.length() while (index >= 0) { - sb.append(inString.substring(pos, index)); - sb.append(newPattern); - pos = index + patLen; - index = inString.indexOf(oldPattern, pos); + sb << inString.substring(pos, index) + sb << newPattern + pos = index + patLen + index = inString.indexOf(oldPattern, pos) } - sb.append(inString.substring(pos)); + sb << inString.substring(pos) // remember to append any characters to the right of a match - return sb.toString(); + return sb.toString() } private static boolean hasLength(CharSequence str) { - return (str != null && str.length() > 0); + return (str != null && !str.isEmpty()) } /** @@ -633,24 +627,24 @@ private static boolean hasLength(CharSequence str) { * @return the URL for the actual jar file * @throws MalformedURLException if no valid jar file URL could be extracted */ - public static URL extractJarFileURL(URL jarUrl) throws MalformedURLException { - String urlFile = jarUrl.getFile(); - int separatorIndex = urlFile.indexOf(JAR_URL_SEPARATOR); + static URL extractJarFileURL(URL jarUrl) throws MalformedURLException { + String urlFile = jarUrl.file + int separatorIndex = urlFile.indexOf(JAR_URL_SEPARATOR) if (separatorIndex != -1) { - String jarFile = urlFile.substring(0, separatorIndex); + String jarFile = urlFile.substring(0, separatorIndex) try { - return new URL(jarFile); + return new URL(jarFile) } catch (MalformedURLException ex) { // Probably no protocol in original jar URL, like "jar:C:/mypath/myjar.jar". // This usually indicates that the jar file resides in the file system. - if (!jarFile.startsWith("/")) { - jarFile = "/" + jarFile; + if (!jarFile.startsWith('/')) { + jarFile = '/' + jarFile } - return new URL(FILE_URL_PREFIX + jarFile); + return new URL(FILE_URL_PREFIX + jarFile) } } - return jarUrl; + return jarUrl } /** @@ -660,8 +654,8 @@ public static URL extractJarFileURL(URL jarUrl) throws MalformedURLException { * @return the URI instance * @throws URISyntaxException if the location wasn't a valid URI */ - public static URI toURI(String location) throws URISyntaxException { - return new URI(replace(location, " ", "%20")); + static URI toURI(String location) throws URISyntaxException { + return new URI(replace(location, ' ', '%20')) } /** @@ -671,19 +665,19 @@ public static URI toURI(String location) throws URISyntaxException { * @return true if it is a Grails path */ - public static boolean isGrailsPath(String path) { + static boolean isGrailsPath(String path) { if (KNOWN_PATHS.containsKey(path)) { - return KNOWN_PATHS.get(path); + return KNOWN_PATHS[path] } - for (Pattern grailsAppResourcePattern : grailsAppResourcePatterns) { - Matcher m = grailsAppResourcePattern.matcher(path); + for (Pattern grailsAppResourcePattern in grailsAppResourcePatterns) { + Matcher m = grailsAppResourcePattern.matcher(path) if (m.find()) { - KNOWN_PATHS.put(path, true); - return true; + KNOWN_PATHS.put(path, true) + return true } } - KNOWN_PATHS.put(path, false); - return false; + KNOWN_PATHS.put(path, false) + return false } /** @@ -692,14 +686,14 @@ public static boolean isGrailsPath(String path) { * @param path The path to check * @return true if it is a Grails path */ - public static boolean isProjectSource(String path) { - for (Pattern grailsAppResourcePattern : patterns) { - Matcher m = grailsAppResourcePattern.matcher(path); + static boolean isProjectSource(String path) { + for (Pattern grailsAppResourcePattern in patterns) { + Matcher m = grailsAppResourcePattern.matcher(path) if (m.find()) { - return true; + return true } } - return false; + return false } /** @@ -708,13 +702,13 @@ public static boolean isProjectSource(String path) { * @param r The resoruce to check * @return true if it is a Grails path */ - public static boolean isProjectSource(Resource r) { + static boolean isProjectSource(Resource r) { try { - String file = r.getURL().getFile(); - return isProjectSource(file) || file.endsWith("GrailsPlugin.groovy"); + String file = r.URL.file + return isProjectSource(file) || file.endsWith('GrailsPlugin.groovy') } catch (IOException e) { - return false; + return false } } @@ -724,58 +718,58 @@ public static boolean isProjectSource(Resource r) { * @param r The resource to check * @return True if it is a Grails resource */ - public static boolean isGrailsResource(Resource r) { + static boolean isGrailsResource(Resource r) { try { - String file = r.getURL().getFile(); - return isGrailsPath(file) || file.endsWith("GrailsPlugin.groovy"); + String file = r.URL.file + return isGrailsPath(file) || file.endsWith('GrailsPlugin.groovy') } catch (IOException e) { - return false; + return false } } - public static Resource getViewsDir(Resource resource) { - if (resource == null) return null; + static Resource getViewsDir(Resource resource) { + if (resource == null) return null - Resource appDir = getAppDir(resource); - if (appDir == null) return null; - return appDir.createRelative("views"); + Resource appDir = getAppDir(resource) + if (appDir == null) return null + return appDir.createRelative('views') } - public static Resource getAppDir(Resource resource) { - if (resource == null) return null; + static Resource getAppDir(Resource resource) { + if (resource == null) return null try { - File file = resource.getFile(); - while (file != null && !file.getName().equals(GRAILS_APP_DIR)) { - file = file.getParentFile(); + File file = resource.file + while (file != null && file.name != GRAILS_APP_DIR) { + file = file.parentFile } if (file != null) { - return new FileSystemResource(file.getAbsolutePath() + '/'); + return new FileSystemResource(file.absolutePath + '/') } - } catch (IOException e) { + } catch (IOException ignored) { } try { - String url = resource.getURL().toString(); + String url = resource.URL.toString() - int i = url.lastIndexOf(GRAILS_APP_DIR); + int i = url.lastIndexOf(GRAILS_APP_DIR) if (i > -1) { - url = url.substring(0, i + 10); - return new UrlResource(url + '/'); + url = url.substring(0, i + 10) + return new UrlResource(url + '/') } - return null; + return null } catch (MalformedURLException e) { - return null; + return null } catch (IOException e) { - return null; + return null } } - private static final Pattern PLUGIN_PATTERN = Pattern.compile(".+?(/plugins/.+?/" + GRAILS_APP_DIR + "/.+)"); + private static final Pattern PLUGIN_PATTERN = Pattern.compile('.+?(/plugins/.+?/' + GRAILS_APP_DIR + '/.+)') /** * Takes a Grails resource (one located inside the grails-app dir) and gets its relative path inside the WEB-INF directory @@ -784,33 +778,33 @@ public static Resource getAppDir(Resource resource) { * @param resource The Grails resource, which is a file inside the grails-app dir * @return The relative URL of the file inside the WEB-INF dir at deployment time or null if it cannot be established */ - public static String getRelativeInsideWebInf(Resource resource) { - if (resource == null) return null; + static String getRelativeInsideWebInf(Resource resource) { + if (resource == null) return null try { - String url = resource.getURL().toString(); - int i = url.indexOf(WEB_INF); + String url = resource.URL.toString() + int i = url.indexOf(WEB_INF) if (i > -1) { - return url.substring(i); + return url.substring(i) } - Matcher m = PLUGIN_PATTERN.matcher(url); + Matcher m = PLUGIN_PATTERN.matcher(url) if (m.find()) { - return WEB_INF + m.group(1); + return WEB_INF + m.group(1) } - i = url.lastIndexOf(GRAILS_APP_DIR); + i = url.lastIndexOf(GRAILS_APP_DIR) if (i > -1) { - return WEB_INF + "/" + url.substring(i); + return WEB_INF + '/' + url.substring(i) } } catch (IOException e) { - return null; + return null } - return null; + return null } - private static final Pattern PLUGIN_RESOURCE_PATTERN = Pattern.compile(".+?/(plugins/.+?)/" + GRAILS_APP_DIR + "/.+"); + private static final Pattern PLUGIN_RESOURCE_PATTERN = Pattern.compile('.+?/(plugins/.+?)/' + GRAILS_APP_DIR + '/.+') /** * Retrieves the static resource path for the given Grails resource artifact (controller/taglib etc.) @@ -819,25 +813,25 @@ public static String getRelativeInsideWebInf(Resource resource) { * @param contextPath The additonal context path to prefix * @return The resource path */ - public static String getStaticResourcePathForResource(Resource resource, String contextPath) { + static String getStaticResourcePathForResource(Resource resource, String contextPath) { - if (contextPath == null) contextPath = ""; - if (resource == null) return contextPath; + if (contextPath == null) contextPath = '' + if (resource == null) return contextPath - String url; + String url try { - url = resource.getURL().toString(); + url = resource.URL.toString() } catch (IOException e) { - return contextPath; + return contextPath } - Matcher m = PLUGIN_RESOURCE_PATTERN.matcher(url); + Matcher m = PLUGIN_RESOURCE_PATTERN.matcher(url) if (m.find()) { - return (contextPath.length() > 0 ? contextPath + "/" : "") + m.group(1); + return (!contextPath.isEmpty() ? contextPath + '/' : '') + m.group(1) } - return contextPath; + return contextPath } /** @@ -852,16 +846,16 @@ public static String getStaticResourcePathForResource(Resource resource, String * @param path The path to evaluate * @return The path relative to the root folder grails-app */ - public static String getPathFromRoot(String path) { - for (Pattern COMPILER_ROOT_PATTERN : COMPILER_ROOT_PATTERNS) { - Matcher m = COMPILER_ROOT_PATTERN.matcher(path); + static String getPathFromRoot(String path) { + for (Pattern COMPILER_ROOT_PATTERN in COMPILER_ROOT_PATTERNS) { + Matcher m = COMPILER_ROOT_PATTERN.matcher(path) if (m.find()) { // the spring scripts pattern captures the relative path as its only group; the resource pattern // captures the artefact directory before it and the extension after it - return m.group(m.groupCount() == 1 ? 1 : m.groupCount() - 1); + return m.group(m.groupCount() == 1 ? 1 : m.groupCount() - 1) } } - return null; + return null } /** @@ -873,24 +867,24 @@ public static String getPathFromRoot(String path) { * @param path The path * @return The path relative to the base directory or null if it can't be established */ - public static String getPathFromBaseDir(String path) { - int i = path.indexOf("grails-app/"); + static String getPathFromBaseDir(String path) { + int i = path.indexOf('grails-app/') if (i > -1) { - return path.substring(i + 11); + return path.substring(i + 11) } else { try { - File baseDir = BuildSettings.BASE_DIR; - String basePath = baseDir != null ? baseDir.getCanonicalPath() : null; + File baseDir = BuildSettings.BASE_DIR + String basePath = baseDir != null ? baseDir.canonicalPath : null if (basePath != null) { - String canonicalPath = new File(path).getCanonicalPath(); - return canonicalPath.contains(basePath) ? canonicalPath.substring(basePath.length() + 1) : canonicalPath; + String canonicalPath = new File(path).canonicalPath + return canonicalPath.contains(basePath) ? canonicalPath.substring(basePath.length() + 1) : canonicalPath } - } catch (IOException e) { + } catch (IOException ignored) { // ignore } } - return null; + return null } /** @@ -902,15 +896,15 @@ public static String getPathFromBaseDir(String path) { * @param path The path * @return The domain or null if not known */ - public static String getArtefactDirectory(String path) { + static String getArtefactDirectory(String path) { if (path != null) { - final Matcher matcher = RESOURCE_PATH_PATTERN.matcher(path); + final Matcher matcher = RESOURCE_PATH_PATTERN.matcher(path) if (matcher.find()) { - return matcher.group(1); + return matcher.group(1) } } - return null; + return null } /** @@ -938,48 +932,49 @@ public static String getArtefactDirectory(String path) { * @param pieces Strings to concatenate together into a uri * @return a uri */ - public static String appendPiecesForUri(String... pieces) { - if (pieces == null || pieces.length == 0) return ""; + static String appendPiecesForUri(String... pieces) { + if (pieces == null || pieces.length == 0) return '' // join parts && strip double slashes - StringBuilder builder = new StringBuilder(16 * pieces.length); - char previous = 0; + StringBuilder builder = new StringBuilder(16 * pieces.length) + char previous = 0 for (int i = 0; i < pieces.length; i++) { - String piece = pieces[i]; - if (piece != null && piece.length() > 0) { + String piece = pieces[i] + if (piece != null && !piece.isEmpty()) { for (int j = 0, maxlen = piece.length(); j < maxlen; j++) { - char current = piece.charAt(j); + char current = piece.charAt(j) if (!(previous == '/' && current == '/')) { - builder.append(current); - previous = current; + builder << current + previous = current } } if (i + 1 < pieces.length && previous != '/') { - builder.append('/'); - previous = '/'; + builder << '/' + previous = '/' } } } - return builder.toString(); + return builder.toString() } - @SuppressWarnings("unchecked") - public static Object instantiateFromConfig(ConfigObject config, String configKey, String defaultClassName) + @SuppressWarnings('unchecked') + static Object instantiateFromConfig(ConfigObject config, String configKey, String defaultClassName) throws InstantiationException, IllegalAccessException, ClassNotFoundException, LinkageError { - return instantiateFromFlatConfig(config.flatten(), configKey, defaultClassName); + return instantiateFromFlatConfig(config.flatten(), configKey, defaultClassName) } - public static Object instantiateFromFlatConfig(Map flatConfig, String configKey, String defaultClassName) + static Object instantiateFromFlatConfig(Map flatConfig, String configKey, String defaultClassName) throws InstantiationException, IllegalAccessException, ClassNotFoundException, LinkageError { - String className = defaultClassName; - Object configName = flatConfig.get(configKey); + String className = defaultClassName + Object configName = flatConfig[configKey] if (configName instanceof CharSequence) { - className = configName.toString(); + className = configName.toString() } - return forName(className, DefaultResourceLoader.getDefaultClassLoader()).newInstance(); + return forName(className, DefaultResourceLoader.defaultClassLoader).newInstance() } private static Class forName(String className, ClassLoader defaultClassLoader) throws ClassNotFoundException { - return defaultClassLoader.loadClass(className); + return defaultClassLoader.loadClass(className) } + } diff --git a/grails-gradle/model/src/main/groovy/org/grails/io/support/Resource.java b/grails-gradle/model/src/main/groovy/org/grails/io/support/Resource.groovy similarity index 86% rename from grails-gradle/model/src/main/groovy/org/grails/io/support/Resource.java rename to grails-gradle/model/src/main/groovy/org/grails/io/support/Resource.groovy index 431dd9e48ce..63f27ea42a6 100644 --- a/grails-gradle/model/src/main/groovy/org/grails/io/support/Resource.java +++ b/grails-gradle/model/src/main/groovy/org/grails/io/support/Resource.groovy @@ -16,13 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.grails.io.support; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.net.URL; +package org.grails.io.support /** * Interface for a resource descriptor that abstracts from the actual @@ -38,13 +32,13 @@ * @see #getURI() * @see #getFile() */ -public interface Resource { +interface Resource { /** * An input stream * @return The input stream */ - InputStream getInputStream() throws IOException; + InputStream getInputStream() throws IOException /** * Return whether this resource actually exists in physical form. @@ -52,7 +46,7 @@ public interface Resource { * existence of a Resource handle only guarantees a * valid descriptor handle. */ - boolean exists(); + boolean exists() /** * Return whether the contents of this resource can be read, @@ -63,42 +57,42 @@ public interface Resource { * that the resource content cannot be read. * @see #getInputStream() */ - boolean isReadable(); + boolean isReadable() /** * Return a URL handle for this resource. * @throws java.io.IOException if the resource cannot be resolved as URL, * i.e. if the resource is not available as descriptor */ - URL getURL() throws IOException; + URL getURL() throws IOException /** * Return a URI handle for this resource. * @throws IOException if the resource cannot be resolved as URI, * i.e. if the resource is not available as descriptor */ - URI getURI() throws IOException; + URI getURI() throws IOException /** * Return a File handle for this resource. * @throws IOException if the resource cannot be resolved as absolute * file path, i.e. if the resource is not available in a file system */ - File getFile() throws IOException; + File getFile() throws IOException /** * Determine the content length for this resource. * @throws IOException if the resource cannot be resolved * (in the file system or as some other known physical resource type) */ - long contentLength() throws IOException; + long contentLength() throws IOException /** * Determine the last-modified timestamp for this resource. * @throws IOException if the resource cannot be resolved * (in the file system or as some other known physical resource type) */ - long lastModified() throws IOException; + long lastModified() throws IOException /** * Determine a filename for this resource, i.e. typically the last @@ -106,7 +100,7 @@ public interface Resource { *

Returns null if this type of resource does not * have a filename. */ - String getFilename(); + String getFilename() /** * Return a description for this resource, @@ -115,7 +109,7 @@ public interface Resource { * from their toString method. * @see java.lang.Object#toString() */ - String getDescription(); + String getDescription() /** * Creates a new resource relative to this one @@ -123,5 +117,6 @@ public interface Resource { * @param relativePath The relative path * @return The new resource */ - Resource createRelative(String relativePath); + Resource createRelative(String relativePath) + } diff --git a/grails-gradle/model/src/main/groovy/org/grails/io/support/ResourceLoader.java b/grails-gradle/model/src/main/groovy/org/grails/io/support/ResourceLoader.groovy similarity index 92% rename from grails-gradle/model/src/main/groovy/org/grails/io/support/ResourceLoader.java rename to grails-gradle/model/src/main/groovy/org/grails/io/support/ResourceLoader.groovy index a2180809a87..1bd8e5216ef 100644 --- a/grails-gradle/model/src/main/groovy/org/grails/io/support/ResourceLoader.java +++ b/grails-gradle/model/src/main/groovy/org/grails/io/support/ResourceLoader.groovy @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.grails.io.support; +package org.grails.io.support /** * Strategy interface for loading resources (e.. class path or file system @@ -30,10 +30,10 @@ * @author Juergen Hoeller * @since 10.03.2004 */ -public interface ResourceLoader { +interface ResourceLoader { /** Pseudo URL prefix for loading from the class path: "classpath:" */ - String CLASSPATH_URL_PREFIX = "classpath:"; + String CLASSPATH_URL_PREFIX = 'classpath:' /** * Return a Resource handle for the specified resource. @@ -52,7 +52,7 @@ public interface ResourceLoader { * @return a corresponding Resource handle * @see #CLASSPATH_URL_PREFIX */ - Resource getResource(String location); + Resource getResource(String location) /** * Expose the ClassLoader used by this ResourceLoader. @@ -61,5 +61,6 @@ public interface ResourceLoader { * on the thread context ClassLoader. * @return the ClassLoader (never null) */ - ClassLoader getClassLoader(); + ClassLoader getClassLoader() + } diff --git a/grails-gradle/model/src/main/groovy/org/grails/io/support/SpringIOUtils.java b/grails-gradle/model/src/main/groovy/org/grails/io/support/SpringIOUtils.groovy similarity index 60% rename from grails-gradle/model/src/main/groovy/org/grails/io/support/SpringIOUtils.java rename to grails-gradle/model/src/main/groovy/org/grails/io/support/SpringIOUtils.groovy index b107c03d06b..7adf03a18f8 100644 --- a/grails-gradle/model/src/main/groovy/org/grails/io/support/SpringIOUtils.java +++ b/grails-gradle/model/src/main/groovy/org/grails/io/support/SpringIOUtils.groovy @@ -16,41 +16,25 @@ * specific language governing permissions and limitations * under the License. */ -package org.grails.io.support; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.Closeable; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Reader; -import java.io.StringWriter; -import java.io.Writer; -import java.lang.reflect.Array; -import java.net.URL; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.HashMap; -import java.util.Map; - -import javax.xml.XMLConstants; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; - -import groovy.xml.FactorySupport; -import groovy.xml.XmlSlurper; - -import org.slf4j.LoggerFactory; -import org.xml.sax.SAXException; - -import org.apache.grails.gradle.common.XmlParserFeature; +package org.grails.io.support + +import java.lang.reflect.Array +import java.security.MessageDigest +import java.security.NoSuchAlgorithmException + +import javax.xml.XMLConstants +import javax.xml.parsers.ParserConfigurationException +import javax.xml.parsers.SAXParser +import javax.xml.parsers.SAXParserFactory + +import groovy.transform.CompileStatic +import groovy.xml.FactorySupport +import groovy.xml.XmlSlurper + +import org.slf4j.LoggerFactory +import org.xml.sax.SAXException + +import org.apache.grails.gradle.common.XmlParserFeature /** * Simple utility methods for file and stream copying. @@ -65,21 +49,22 @@ * * @since 06.10.2003 */ -@SuppressWarnings("unchecked") -public class SpringIOUtils { +@SuppressWarnings('unchecked') +@CompileStatic +class SpringIOUtils { - @SuppressWarnings("rawtypes") - private static Map algorithms = new HashMap(); + @SuppressWarnings('rawtypes') + private static Map algorithms = new HashMap() static { - algorithms.put("md5", "MD5"); - algorithms.put("sha1", "SHA-1"); + algorithms.put('md5', 'MD5') + algorithms.put('sha1', 'SHA-1') } // byte to hex string converter - private static final char[] CHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'a', 'b', 'c', 'd', 'e', 'f'}; - public static final int BUFFER_SIZE = 4096; + private static final char[] CHARS = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f'] as char[] + public static final int BUFFER_SIZE = 4096 /** * Convert a byte[] array to readable string format. This makes the "hex" readable! @@ -88,61 +73,61 @@ public class SpringIOUtils { * @param in * byte[] buffer to convert to string format */ - public static String byteArrayToHexString(byte[] in) { - byte ch = 0x00; + static String byteArrayToHexString(byte[] in) { + byte ch = 0x00 if (in == null || in.length <= 0) { - return null; + return null } - StringBuilder out = new StringBuilder(in.length * 2); + StringBuilder out = new StringBuilder(in.length * 2) //CheckStyle:MagicNumber OFF for (int i = 0; i < in.length; i++) { - ch = (byte) (in[i] & 0xF0); // Strip off high nibble - ch = (byte) (ch >>> 4); // shift the bits down - ch = (byte) (ch & 0x0F); // must do this is high order bit is on! + ch = (byte) (in[i] & 0xF0) // Strip off high nibble + ch = (byte) (ch >>> 4) // shift the bits down + ch = (byte) (ch & 0x0F) // must do this is high order bit is on! - out.append(CHARS[ch]); // convert the nibble to a String Character - ch = (byte) (in[i] & 0x0F); // Strip off low nibble - out.append(CHARS[ch]); // convert the nibble to a String Character + out << CHARS[ch] // convert the nibble to a String Character + ch = (byte) (in[i] & 0x0F) // Strip off low nibble + out << CHARS[ch] // convert the nibble to a String Character } //CheckStyle:MagicNumber ON - return out.toString(); + return out.toString() } - public static String computeChecksum(File f, String algorithm) throws IOException { - return byteArrayToHexString(compute(f, algorithm)); + static String computeChecksum(File f, String algorithm) throws IOException { + return byteArrayToHexString(compute(f, algorithm)) } private static byte[] compute(File f, String algorithm) throws IOException { - InputStream is = new FileInputStream(f); + InputStream is = new FileInputStream(f) try { - MessageDigest md = getMessageDigest(algorithm); - md.reset(); + MessageDigest md = getMessageDigest(algorithm) + md.reset() - byte[] buf = new byte[BUFFER_SIZE]; - int len = 0; + byte[] buf = new byte[BUFFER_SIZE] + int len = 0 while ((len = is.read(buf)) != -1) { - md.update(buf, 0, len); + md.update(buf, 0, len) } - return md.digest(); + return md.digest() } finally { - is.close(); + is.close() } } private static MessageDigest getMessageDigest(String algorithm) { - String mdAlgorithm = (String) algorithms.get(algorithm); + String mdAlgorithm = (String) algorithms[algorithm] if (mdAlgorithm == null) { - throw new IllegalArgumentException("unknown algorithm " + algorithm); + throw new IllegalArgumentException("unknown algorithm ${algorithm}") } try { - return MessageDigest.getInstance(mdAlgorithm); + return MessageDigest.getInstance(mdAlgorithm) } catch (NoSuchAlgorithmException e) { - throw new IllegalArgumentException("unknown algorithm " + algorithm); + throw new IllegalArgumentException("unknown algorithm ${algorithm}") } } @@ -153,20 +138,20 @@ private static MessageDigest getMessageDigest(String algorithm) { * @param array2 The source array * @return The new array */ - public static Object[] addAll(Object[] array1, Object[] array2) { - Object[] joinedArray = (Object[]) Array.newInstance(array1.getClass().getComponentType(), array1.length + array2.length); - System.arraycopy(array1, 0, joinedArray, 0, array1.length); + static Object[] addAll(Object[] array1, Object[] array2) { + Object[] joinedArray = (Object[]) Array.newInstance(array1.getClass().componentType, array1.length + array2.length) + System.arraycopy(array1, 0, joinedArray, 0, array1.length) try { - System.arraycopy(array2, 0, joinedArray, array1.length, array2.length); + System.arraycopy(array2, 0, joinedArray, array1.length, array2.length) } catch (ArrayStoreException ase) { - final Class type1 = array1.getClass().getComponentType(); - final Class type2 = array2.getClass().getComponentType(); + final Class type1 = array1.getClass().componentType + final Class type2 = array2.getClass().componentType if (!type1.isAssignableFrom(type2)) { - throw new IllegalArgumentException("Cannot store " + type2.getName() + " in an array of " + type1.getName()); + throw new IllegalArgumentException("Cannot store ${type2.name} in an array of ${type1.name}") } - throw ase; // No, so rethrow original + throw ase // No, so rethrow original } - return joinedArray; + return joinedArray } //--------------------------------------------------------------------- @@ -181,12 +166,12 @@ public static Object[] addAll(Object[] array1, Object[] array2) { * @param resources The resources to copy * @param targetDir The target directory */ - public static void copyAll(Resource base, Resource[] resources, File targetDir) throws IOException { - final URL baseUrl = base.getURL(); - for (Resource resource : resources) { - final InputStream input = resource.getInputStream(); - final File target = new File(targetDir, resource.getURL().toString().substring(baseUrl.toString().length())); - copy(new BufferedInputStream(input), new BufferedOutputStream(new FileOutputStream(target))); + static void copyAll(Resource base, Resource[] resources, File targetDir) throws IOException { + final URL baseUrl = base.URL + for (Resource resource in resources) { + final InputStream input = resource.inputStream + final File target = new File(targetDir, resource.URL.toString().substring(baseUrl.toString().length())) + copy(new BufferedInputStream(input), new BufferedOutputStream(new FileOutputStream(target))) } } @@ -197,11 +182,11 @@ public static void copyAll(Resource base, Resource[] resources, File targetDir) * @return the number of bytes copied * @throws java.io.IOException in case of I/O errors */ - public static int copy(File in, File out) throws IOException { - assert in != null : "No input File specified"; - assert out != null : "No output File specified"; + static int copy(File in, File out) throws IOException { + Objects.requireNonNull(in, 'No input File specified') + Objects.requireNonNull(out, 'No output File specified') return copy(new BufferedInputStream(new FileInputStream(in)), - new BufferedOutputStream(new FileOutputStream(out))); + new BufferedOutputStream(new FileOutputStream(out))) } /** @@ -211,11 +196,11 @@ public static int copy(File in, File out) throws IOException { * @return the number of bytes copied * @throws java.io.IOException in case of I/O errors */ - public static int copy(Resource in, File out) throws IOException { - assert in != null : "No input File specified"; - assert out != null : "No output File specified"; - return copy(new BufferedInputStream(in.getInputStream()), - new BufferedOutputStream(new FileOutputStream(out))); + static int copy(Resource in, File out) throws IOException { + Objects.requireNonNull(in, 'No input File specified') + Objects.requireNonNull(out, 'No output File specified') + return copy(new BufferedInputStream(in.inputStream), + new BufferedOutputStream(new FileOutputStream(out))) } /** @@ -224,12 +209,12 @@ public static int copy(Resource in, File out) throws IOException { * @param out the file to copy to * @throws IOException in case of I/O errors */ - public static void copy(byte[] in, File out) throws IOException { - assert in != null : "No input byte array specified"; - assert out != null : "No output File specified"; - ByteArrayInputStream inStream = new ByteArrayInputStream(in); - OutputStream outStream = new BufferedOutputStream(new FileOutputStream(out)); - copy(inStream, outStream); + static void copy(byte[] in, File out) throws IOException { + Objects.requireNonNull(in, 'No input byte array specified') + Objects.requireNonNull(out, 'No output File specified') + ByteArrayInputStream inStream = new ByteArrayInputStream(in) + OutputStream outStream = new BufferedOutputStream(new FileOutputStream(out)) + copy(inStream, outStream) } /** @@ -238,10 +223,10 @@ public static void copy(byte[] in, File out) throws IOException { * @return the new byte array that has been copied to * @throws IOException in case of I/O errors */ - public static byte[] copyToByteArray(File in) throws IOException { - assert in != null : "No input File specified"; + static byte[] copyToByteArray(File in) throws IOException { + Objects.requireNonNull(in, 'No input File specified') - return copyToByteArray(new BufferedInputStream(new FileInputStream(in))); + return copyToByteArray(new BufferedInputStream(new FileInputStream(in))) } //--------------------------------------------------------------------- @@ -256,30 +241,30 @@ public static byte[] copyToByteArray(File in) throws IOException { * @return the number of bytes copied * @throws IOException in case of I/O errors */ - public static int copy(InputStream in, OutputStream out) throws IOException { - assert in != null : "No input stream specified"; - assert out != null : "No output stream specified"; + static int copy(InputStream in, OutputStream out) throws IOException { + Objects.requireNonNull(in, 'No input stream specified') + Objects.requireNonNull(out, 'No output stream specified') try { - int byteCount = 0; - byte[] buffer = new byte[BUFFER_SIZE]; - int bytesRead = -1; + int byteCount = 0 + byte[] buffer = new byte[BUFFER_SIZE] + int bytesRead = -1 while ((bytesRead = in.read(buffer)) != -1) { - out.write(buffer, 0, bytesRead); - byteCount += bytesRead; + out.write(buffer, 0, bytesRead) + byteCount += bytesRead } - out.flush(); - return byteCount; + out.flush() + return byteCount } finally { try { - in.close(); + in.close() } - catch (IOException ex) { + catch (IOException ignored) { } try { - out.close(); + out.close() } - catch (IOException ex) { + catch (IOException ignored) { } } } @@ -291,17 +276,17 @@ public static int copy(InputStream in, OutputStream out) throws IOException { * @param out the OutputStream to copy to * @throws IOException in case of I/O errors */ - public static void copy(byte[] in, OutputStream out) throws IOException { - assert in != null : "No input byte array specified"; - assert out != null : "No output stream specified"; + static void copy(byte[] in, OutputStream out) throws IOException { + Objects.requireNonNull(in, 'No input byte array specified') + Objects.requireNonNull(out, 'No output stream specified') try { - out.write(in); + out.write(in) } finally { try { - out.close(); + out.close() } - catch (IOException ex) { + catch (IOException ignored) { } } } @@ -313,10 +298,10 @@ public static void copy(byte[] in, OutputStream out) throws IOException { * @return the new byte array that has been copied to * @throws IOException in case of I/O errors */ - public static byte[] copyToByteArray(InputStream in) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(BUFFER_SIZE); - copy(in, out); - return out.toByteArray(); + static byte[] copyToByteArray(InputStream in) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(BUFFER_SIZE) + copy(in, out) + return out.toByteArray() } //--------------------------------------------------------------------- @@ -331,31 +316,31 @@ public static byte[] copyToByteArray(InputStream in) throws IOException { * @return the number of characters copied * @throws IOException in case of I/O errors */ - public static int copy(Reader in, Writer out) throws IOException { - assert in != null : "No input Reader specified"; - assert out != null : "No output Writer specified"; + static int copy(Reader in, Writer out) throws IOException { + Objects.requireNonNull(in, 'No input Reader specified') + Objects.requireNonNull(out, 'No output Writer specified') try { - int byteCount = 0; - char[] buffer = new char[BUFFER_SIZE]; - int bytesRead = -1; + int byteCount = 0 + char[] buffer = new char[BUFFER_SIZE] + int bytesRead = -1 while ((bytesRead = in.read(buffer)) != -1) { - out.write(buffer, 0, bytesRead); - byteCount += bytesRead; + out.write(buffer, 0, bytesRead) + byteCount += bytesRead } - out.flush(); - return byteCount; + out.flush() + return byteCount } finally { try { - in.close(); + in.close() } - catch (IOException ex) { + catch (IOException ignored) { } try { - out.close(); + out.close() } - catch (IOException ex) { + catch (IOException ignored) { } } } @@ -367,18 +352,18 @@ public static int copy(Reader in, Writer out) throws IOException { * @param out the Writer to copy to * @throws IOException in case of I/O errors */ - public static void copy(String in, Writer out) throws IOException { - assert in != null : "No input String specified"; - assert out != null : "No output Writer specified"; + static void copy(String in, Writer out) throws IOException { + Objects.requireNonNull(in, 'No input String specified') + Objects.requireNonNull(out, 'No output Writer specified') try { - out.write(in); + out.write(in) } finally { try { - out.close(); + out.close() } - catch (IOException ex) { + catch (IOException ignored) { } } } @@ -388,11 +373,10 @@ public static void copy(String in, Writer out) throws IOException { * * @param closeable The closeable */ - public static void closeQuietly(Closeable closeable) { + static void closeQuietly(Closeable closeable) { try { - if (closeable != null) - closeable.close(); - } catch (IOException e) { + closeable?.close() + } catch (IOException ignored) { // ignore } } @@ -404,10 +388,10 @@ public static void closeQuietly(Closeable closeable) { * @return the String that has been copied to * @throws IOException in case of I/O errors */ - public static String copyToString(Reader in) throws IOException { - StringWriter out = new StringWriter(); - copy(in, out); - return out.toString(); + static String copyToString(Reader in) throws IOException { + StringWriter out = new StringWriter() + copy(in, out) + return out.toString() } /** @@ -419,8 +403,8 @@ public static String copyToString(Reader in) throws IOException { * * @return a namespace-aware, non-validating slurper that rejects {@code DOCTYPE} declarations */ - public static XmlSlurper createXmlSlurper() throws ParserConfigurationException, SAXException { - return createXmlSlurper(false); + static XmlSlurper createXmlSlurper() throws ParserConfigurationException, SAXException { + return createXmlSlurper(false) } /** @@ -441,8 +425,8 @@ public static XmlSlurper createXmlSlurper() throws ParserConfigurationException, * @return a namespace-aware, non-validating slurper with the hardening described above * @since 8.0.0 */ - public static XmlSlurper createXmlSlurper(boolean allowDocTypeDeclaration) throws ParserConfigurationException, SAXException { - return new XmlSlurper(newSAXParser(allowDocTypeDeclaration)); + static XmlSlurper createXmlSlurper(boolean allowDocTypeDeclaration) throws ParserConfigurationException, SAXException { + return new XmlSlurper(newSAXParser(allowDocTypeDeclaration)) } /** @@ -453,8 +437,8 @@ public static XmlSlurper createXmlSlurper(boolean allowDocTypeDeclaration) throw * * @return a namespace-aware, non-validating parser that rejects {@code DOCTYPE} declarations */ - public static SAXParser newSAXParser() throws ParserConfigurationException, SAXException { - return newSAXParser(false); + static SAXParser newSAXParser() throws ParserConfigurationException, SAXException { + return newSAXParser(false) } /** @@ -466,8 +450,8 @@ public static SAXParser newSAXParser() throws ParserConfigurationException, SAXE * @return a namespace-aware, non-validating parser * @since 8.0.0 */ - public static SAXParser newSAXParser(boolean allowDocTypeDeclaration) throws ParserConfigurationException, SAXException { - return parserFactory(allowDocTypeDeclaration).newSAXParser(); + static SAXParser newSAXParser(boolean allowDocTypeDeclaration) throws ParserConfigurationException, SAXException { + return parserFactory(allowDocTypeDeclaration).newSAXParser() } /** @@ -476,49 +460,49 @@ public static SAXParser newSAXParser(boolean allowDocTypeDeclaration) throws Par *

{@link XmlParserFeature#DISALLOW_DOCTYPE_DECL} is handled separately because it is the * one feature that differs between the two parsers; see {@link #createXmlSlurper(boolean)}. */ - private static final XmlParserFeature[] DISABLED_PARSER_FEATURES = { + private static final XmlParserFeature[] DISABLED_PARSER_FEATURES = [ XmlParserFeature.EXTERNAL_GENERAL_ENTITIES, XmlParserFeature.EXTERNAL_PARAMETER_ENTITIES, XmlParserFeature.LOAD_DTD_GRAMMAR, XmlParserFeature.LOAD_EXTERNAL_DTD - }; + ] as XmlParserFeature[] - private static volatile SAXParserFactory strictParserFactory; + private static volatile SAXParserFactory strictParserFactory - private static volatile SAXParserFactory docTypeParserFactory; + private static volatile SAXParserFactory docTypeParserFactory private static SAXParserFactory parserFactory(boolean allowDocTypeDeclaration) throws ParserConfigurationException { if (allowDocTypeDeclaration) { - SAXParserFactory factory = docTypeParserFactory; + SAXParserFactory factory = docTypeParserFactory if (factory == null) { - factory = buildParserFactory(true); - docTypeParserFactory = factory; + factory = buildParserFactory(true) + docTypeParserFactory = factory } - return factory; + return factory } - SAXParserFactory factory = strictParserFactory; + SAXParserFactory factory = strictParserFactory if (factory == null) { - factory = buildParserFactory(false); - strictParserFactory = factory; + factory = buildParserFactory(false) + strictParserFactory = factory } - return factory; + return factory } private static SAXParserFactory buildParserFactory(boolean allowDocTypeDeclaration) throws ParserConfigurationException { - SAXParserFactory factory = FactorySupport.createSaxParserFactory(); - factory.setNamespaceAware(true); - factory.setValidating(false); + SAXParserFactory factory = FactorySupport.createSaxParserFactory() + factory.setNamespaceAware(true) + factory.setValidating(false) try { - factory.setXIncludeAware(false); - } catch (UnsupportedOperationException e) { + factory.setXIncludeAware(false) + } catch (UnsupportedOperationException ignored) { // a parser without XInclude support cannot expand an include either } - setFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true); - setFeature(factory, XmlParserFeature.DISALLOW_DOCTYPE_DECL.getFeatureName(), !allowDocTypeDeclaration); - for (XmlParserFeature feature : DISABLED_PARSER_FEATURES) { - setFeature(factory, feature.getFeatureName(), false); + setFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true) + setFeature(factory, XmlParserFeature.DISALLOW_DOCTYPE_DECL.featureName, !allowDocTypeDeclaration) + for (XmlParserFeature feature in DISABLED_PARSER_FEATURES) { + setFeature(factory, feature.featureName, false) } - return factory; + return factory } /** @@ -532,11 +516,12 @@ private static SAXParserFactory buildParserFactory(boolean allowDocTypeDeclarati */ private static void setFeature(SAXParserFactory factory, String name, boolean value) { try { - factory.setFeature(name, value); + factory.setFeature(name, value) } catch (ParserConfigurationException | SAXException e) { - LoggerFactory.getLogger(SpringIOUtils.class).warn( - "XML parser factory [{}] does not support feature [{}]: {}", - factory.getClass().getName(), name, e.getMessage()); + LoggerFactory.getLogger(SpringIOUtils).warn( + 'XML parser factory [{}] does not support feature [{}]: {}', + factory.getClass().name, name, e.message) } } + } diff --git a/grails-gradle/model/src/main/groovy/org/grails/io/support/UrlResource.java b/grails-gradle/model/src/main/groovy/org/grails/io/support/UrlResource.groovy similarity index 65% rename from grails-gradle/model/src/main/groovy/org/grails/io/support/UrlResource.java rename to grails-gradle/model/src/main/groovy/org/grails/io/support/UrlResource.groovy index f2ba1989d9f..2730ede6c7e 100644 --- a/grails-gradle/model/src/main/groovy/org/grails/io/support/UrlResource.java +++ b/grails-gradle/model/src/main/groovy/org/grails/io/support/UrlResource.groovy @@ -16,16 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -package org.grails.io.support; +package org.grails.io.support -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URL; -import java.net.URLConnection; +import groovy.transform.CompileStatic /** * Resource implementation for java.net.URL locators. @@ -36,31 +29,32 @@ * @since 28.12.2003 * @see java.net.URL */ -public class UrlResource extends AbstractFileResolvingResource { +@CompileStatic +class UrlResource extends AbstractFileResolvingResource { /** * Original URL, used for actual access. */ - private final URL url; + private final URL url /** * Cleaned URL (with normalized path), used for comparisons. */ - private final URL cleanedUrl; + private final URL cleanedUrl /** * Original URI, if available; used for URI and File access. */ - private final URI uri; + private final URI uri /** * Create a new UrlResource. * @param url a URL */ - public UrlResource(URL url) { - this.url = url; - cleanedUrl = getCleanedUrl(url, url.toString()); - uri = null; + UrlResource(URL url) { + this.url = url + cleanedUrl = getCleanedUrl(url, url.toString()) + uri = null } /** @@ -68,10 +62,10 @@ public UrlResource(URL url) { * @param uri a URI * @throws java.net.MalformedURLException if the given URL path is not valid */ - public UrlResource(URI uri) throws MalformedURLException { - url = uri.toURL(); - cleanedUrl = getCleanedUrl(url, uri.toString()); - this.uri = uri; + UrlResource(URI uri) throws MalformedURLException { + url = uri.toURL() + cleanedUrl = getCleanedUrl(url, uri.toString()) + this.uri = uri } /** @@ -79,10 +73,10 @@ public UrlResource(URI uri) throws MalformedURLException { * @param path a URL path * @throws MalformedURLException if the given URL path is not valid */ - public UrlResource(String path) throws MalformedURLException { - url = new URL(path); - cleanedUrl = getCleanedUrl(url, path); - uri = null; + UrlResource(String path) throws MalformedURLException { + url = new URL(path) + cleanedUrl = getCleanedUrl(url, path) + uri = null } /** @@ -93,12 +87,12 @@ public UrlResource(String path) throws MalformedURLException { */ private URL getCleanedUrl(URL originalUrl, String originalPath) { try { - return new URL(GrailsResourceUtils.cleanPath(originalPath)); + return new URL(GrailsResourceUtils.cleanPath(originalPath)) } catch (MalformedURLException ex) { // Cleaned URL path cannot be converted to URL // -> take original URL. - return originalUrl; + return originalUrl } } @@ -110,41 +104,41 @@ private URL getCleanedUrl(URL originalUrl, String originalPath) { * @see java.net.URLConnection#setUseCaches(boolean) * @see java.net.URLConnection#getInputStream() */ - public InputStream getInputStream() throws IOException { - URLConnection con = url.openConnection(); - useCachesIfNecessary(con); + InputStream getInputStream() throws IOException { + URLConnection con = url.openConnection() + useCachesIfNecessary(con) try { - return con.getInputStream(); + return con.inputStream } catch (IOException ex) { // Close the HTTP connection (if applicable). if (con instanceof HttpURLConnection) { - ((HttpURLConnection) con).disconnect(); + con.disconnect() } - throw ex; + throw ex } } private static void useCachesIfNecessary(URLConnection con) { - con.setUseCaches(con.getClass().getName().startsWith("JNLP")); + con.useCaches = con.getClass().name.startsWith('JNLP') } /** * This implementation returns the underlying URL reference. */ - public URL getURL() throws IOException { - return url; + URL getURL() throws IOException { + return url } /** * This implementation returns the underlying URI directly, * if possible. */ - public URI getURI() throws IOException { + URI getURI() throws IOException { if (uri != null) { - return uri; + return uri } - return getFile().toURI(); + return getFile().toURI() } /** @@ -152,8 +146,8 @@ public URI getURI() throws IOException { * provided that it refers to a file in the file system. */ @Override - public File getFile() throws IOException { - return uri == null ? super.getFile() : super.getFile(uri); + File getFile() throws IOException { + return uri == null ? super.getFile() : super.getFile(uri) } /** @@ -161,14 +155,14 @@ public File getFile() throws IOException { * relative to the path of the underlying URL of this resource descriptor. * @see java.net.URL#URL(java.net.URL, String) */ - public Resource createRelative(String relativePath) { - if (relativePath.startsWith("/")) { - relativePath = relativePath.substring(1); + Resource createRelative(String relativePath) { + if (relativePath.startsWith('/')) { + relativePath = relativePath.substring(1) } try { - return new UrlResource(new URL(url, relativePath)); + return new UrlResource(new URL(url, relativePath)) } catch (MalformedURLException e) { - return null; + return null } } @@ -177,36 +171,37 @@ public Resource createRelative(String relativePath) { * @see java.net.URL#getFile() * @see java.io.File#getName() */ - public String getFilename() { - return new File(url.getFile()).getName(); + String getFilename() { + return new File(url.file).name } /** * This implementation returns a description that includes the URL. */ - public String getDescription() { - return "URL [" + url + "]"; + String getDescription() { + return "URL [${url}]" } /** * This implementation compares the underlying URL references. */ @Override - public boolean equals(Object obj) { - return (obj == this || - (obj instanceof UrlResource && cleanedUrl.equals(((UrlResource) obj).cleanedUrl))); + boolean equals(Object obj) { + return (this.is(obj) || + (obj instanceof UrlResource && cleanedUrl == ((UrlResource) obj).cleanedUrl)) } /** * This implementation returns the hash code of the underlying URL reference. */ @Override - public int hashCode() { - return cleanedUrl.hashCode(); + int hashCode() { + return cleanedUrl.hashCode() } @Override - public String toString() { - return getDescription(); + String toString() { + return getDescription() } + } diff --git a/grails-gradle/model/src/test/groovy/grails/util/CollectionUtilsSpec.groovy b/grails-gradle/model/src/test/groovy/grails/util/CollectionUtilsSpec.groovy new file mode 100644 index 00000000000..b941789fcec --- /dev/null +++ b/grails-gradle/model/src/test/groovy/grails/util/CollectionUtilsSpec.groovy @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package grails.util + +import spock.lang.Specification + +class CollectionUtilsSpec extends Specification { + + void 'newMap builds a map from alternating keys and values'() { + expect: + CollectionUtils.newMap('a', 1, 'b', 2) == [a: 1, b: 2] + } + + void 'newMap returns an empty map for null input'() { + expect: + CollectionUtils.newMap((Object[]) null) == [:] + } + + void 'newMap rejects an odd number of arguments'() { + when: + CollectionUtils.newMap('a', 1, 'b') + + then: + thrown(IllegalArgumentException) + } + + void 'newSet builds a set from varargs'() { + expect: + CollectionUtils.newSet('a', 'b', 'a') == ['a', 'b'] as Set + } + + void 'newSet returns an empty set for null input'() { + expect: + CollectionUtils.newSet((Object[]) null).isEmpty() + } + + void 'newList builds a list from varargs'() { + expect: + CollectionUtils.newList('a', 'b') == ['a', 'b'] + } + + void 'newList returns an empty list for null input'() { + expect: + CollectionUtils.newList((Object[]) null).isEmpty() + } + + void 'getOrCreateChildMap returns the existing child map'() { + given: + Map child = [x: 1] + Map parent = [foo: child] + + expect: + CollectionUtils.getOrCreateChildMap(parent, 'foo').is(child) + } + + void 'getOrCreateChildMap returns a new empty map when the key is absent'() { + given: + Map parent = [:] + + expect: + CollectionUtils.getOrCreateChildMap(parent, 'missing') == [:] + } + + void 'getOrCreateChildMap returns a new empty map when the value is not a map'() { + given: + Map parent = [foo: 'not a map'] + + expect: + CollectionUtils.getOrCreateChildMap(parent, 'foo') == [:] + } + + void 'the utility class cannot be instantiated'() { + given: + def constructor = CollectionUtils.getDeclaredConstructor() + + expect: + java.lang.reflect.Modifier.isPrivate(constructor.modifiers) + + when: + constructor.accessible = true + + then: + constructor.newInstance() != null + } + +} diff --git a/grails-gradle/model/src/test/groovy/grails/util/GrailsNameUtilsSpec.groovy b/grails-gradle/model/src/test/groovy/grails/util/GrailsNameUtilsSpec.groovy new file mode 100644 index 00000000000..9a50e8eaddb --- /dev/null +++ b/grails-gradle/model/src/test/groovy/grails/util/GrailsNameUtilsSpec.groovy @@ -0,0 +1,394 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package grails.util + +import spock.lang.Specification +import spock.lang.Unroll + +class GrailsNameUtilsSpec extends Specification { + + @Unroll + void 'getSetterName(#property) is #expected'() { + expect: + GrailsNameUtils.getSetterName(property) == expected + + where: + property | expected + 'name' | 'setName' + 'aName' | 'setaName' + 'x' | 'setX' + 'URL' | 'setURL' + } + + @Unroll + void 'getGetterName(#property) is #expected'() { + expect: + GrailsNameUtils.getGetterName(property) == expected + + where: + property | expected + 'name' | 'getName' + 'aName' | 'getaName' + 'x' | 'getX' + } + + void 'getClassName combines a logical name and a trailing name'() { + expect: + GrailsNameUtils.getClassName('person', 'Controller') == 'PersonController' + GrailsNameUtils.getClassName('person', null) == 'Person' + GrailsNameUtils.getClassName('person') == 'Person' + } + + @Unroll + void 'getClassName rejects the blank logical name [#name]'() { + when: + GrailsNameUtils.getClassName(name, 'Controller') + + then: + thrown(IllegalArgumentException) + + where: + name << [null, '', ' '] + } + + void 'getFullClassName strips proxy and closure suffixes'() { + expect: + GrailsNameUtils.getFullClassName('com.acme.Foo$_closure1') == 'com.acme.Foo' + GrailsNameUtils.getFullClassName('com.acme.Foo') == 'com.acme.Foo' + GrailsNameUtils.getFullClassName(Map.Entry) == 'java.util.Map' + } + + @Unroll + void 'getClassNameRepresentation(#name) is #expected'() { + expect: + GrailsNameUtils.getClassNameRepresentation(name) == expected + + where: + name | expected + null | '' + '' | '' + 'foo-bar baz' | 'FooBarBaz' + 'foo' | 'Foo' + 'a' | 'A' + 'foo__bar' | 'Foo__bar' + } + + void 'getLogicalName removes the trailing name from a class or class name'() { + expect: + GrailsNameUtils.getLogicalName('PersonController', 'Controller') == 'Person' + GrailsNameUtils.getLogicalName('com.acme.PersonController', 'Controller') == 'Person' + GrailsNameUtils.getLogicalName('Person', 'Controller') == 'Person' + GrailsNameUtils.getLogicalName('PersonController', '') == 'PersonController' + GrailsNameUtils.getLogicalName('PersonController', null) == 'PersonController' + GrailsNameUtils.getLogicalName(ArrayList, 'List') == 'Array' + } + + void 'getLogicalPropertyName lower-cases the logical name'() { + expect: + GrailsNameUtils.getLogicalPropertyName('PersonController', 'Controller') == 'person' + GrailsNameUtils.getLogicalPropertyName('AController', 'Controller') == 'a' + GrailsNameUtils.getLogicalPropertyName('Person', 'Controller') == 'person' + GrailsNameUtils.getLogicalPropertyName('Person', null) == 'person' + } + + @Unroll + void 'getPropertyName(#name) is #expected'() { + expect: + GrailsNameUtils.getPropertyName(name) == expected + GrailsNameUtils.getPropertyNameRepresentation(name) == expected + + where: + name | expected + 'MyClass' | 'myClass' + 'URL' | 'URL' + 'a.b.Foo' | 'foo' + 'Foo Bar' | 'fooBar' + '' | '' + 'X' | 'x' + } + + void 'getPropertyName of a class uses its short name'() { + expect: + GrailsNameUtils.getPropertyName(StringBuilder) == 'stringBuilder' + GrailsNameUtils.getPropertyNameRepresentation(Map.Entry) == 'map$Entry' + } + + @Unroll + void 'getPropertyNameForLowerCaseHyphenSeparatedName(#name) is #expected'() { + expect: + GrailsNameUtils.getPropertyNameForLowerCaseHyphenSeparatedName(name) == expected + + where: + name | expected + 'foo-bar' | 'fooBar' + 'foo' | 'foo' + 'foo--bar-baz' | 'fooBarBaz' + '' | '' + } + + void 'getNameFromScript converts a hyphenated script name to a class name'() { + expect: + GrailsNameUtils.getNameFromScript('my-funk-grails-script') == 'MyFunkGrailsScript' + GrailsNameUtils.getNameFromScript('script') == 'Script' + } + + @Unroll + void 'getShortName and getPackageName split #name'() { + expect: + GrailsNameUtils.getShortName(name) == shortName + GrailsNameUtils.getPackageName(name) == packageName + + where: + name | shortName | packageName + 'a.b.C' | 'C' | 'a.b' + 'C' | 'C' | '' + 'a.C' | 'C' | 'a' + } + + void 'getShortName of a class drops the package'() { + expect: + GrailsNameUtils.getShortName(String) == 'String' + } + + @Unroll + void 'getScriptName(#name) is #expected'() { + expect: + GrailsNameUtils.getScriptName(name) == expected + + where: + name | expected + 'MyFunkyGrailsScript' | 'my-funky-grails-script' + 'MyFunkyScript.groovy' | 'my-funky-script' + 'script' | 'script' + } + + void 'getScriptName tolerates null'() { + expect: + GrailsNameUtils.getScriptName((String) null) == null + GrailsNameUtils.getScriptName((Class) null) == null + GrailsNameUtils.getScriptName(StringBuilder) == 'string-builder' + } + + @Unroll + void 'getPluginName(#descriptor) is #expected'() { + expect: + GrailsNameUtils.getPluginName(descriptor) == expected + + where: + descriptor | expected + 'DbUtilsGrailsPlugin.groovy' | 'db-utils' + 'FooGrailsPlugin.groovy' | 'foo' + '' | '' + null | null + } + + void 'getPluginName rejects a name that is not a plugin descriptor'() { + when: + GrailsNameUtils.getPluginName('Foo.groovy') + + then: + IllegalArgumentException e = thrown() + e.message == 'Plugin descriptor name is not valid: Foo.groovy' + } + + @Unroll + void 'getNaturalName(#name) is #expected'() { + expect: + GrailsNameUtils.getNaturalName(name) == expected + + where: + name | expected + 'firstName' | 'First Name' + 'MyFunkyGrailsScript' | 'My Funky Grails Script' + 'URL' | 'URL' + 'address1' | 'Address1' + 'com.acme.lastName' | 'Last Name' + 'a' | 'A' + } + + @Unroll + void 'isBlank([#value]) is #expected'() { + expect: + GrailsNameUtils.isBlank(value) == expected + + where: + value | expected + null | true + '' | true + ' \t' | true + 'a' | false + ' a ' | false + } + + @Unroll + void 'isValidJavaPackage(#name) is #expected'() { + expect: + GrailsNameUtils.isValidJavaPackage(name) == expected + + where: + name | expected + 'a.b.c' | true + 'abc' | true + 'a..b' | false + '1a.b' | false + 'a.b-c' | false + '' | false + null | false + } + + @Unroll + void 'isValidJavaIdentifier(#name) is #expected'() { + expect: + GrailsNameUtils.isValidJavaIdentifier(name) == expected + + where: + name | expected + 'foo_1' | true + '$bar' | true + '1foo' | false + 'a-b' | false + ' ' | false + null | false + } + + void 'getPropertyNameConvention names scalars after their type'() { + expect: + GrailsNameUtils.getPropertyNameConvention(null) == null + GrailsNameUtils.getPropertyNameConvention('text') == 'string' + GrailsNameUtils.getPropertyNameConvention('text', 'X') == 'stringX' + GrailsNameUtils.getPropertyNameConvention(new StringBuilder()) == 'stringBuilder' + } + + void 'getPropertyNameConvention names collections after their element type'() { + expect: + GrailsNameUtils.getPropertyNameConvention([]) == 'emptyCollection' + GrailsNameUtils.getPropertyNameConvention(['a']) == 'stringList' + GrailsNameUtils.getPropertyNameConvention(['a'], 'X') == 'stringXList' + GrailsNameUtils.getPropertyNameConvention(['a'] as Set) == 'stringSet' + GrailsNameUtils.getPropertyNameConvention(new ArrayDeque(['a'])) == 'stringCollection' + } + + void 'getPropertyNameConvention names maps after their value type'() { + expect: + GrailsNameUtils.getPropertyNameConvention([:]) == 'emptyMap' + GrailsNameUtils.getPropertyNameConvention([k: 'v']) == 'stringMap' + GrailsNameUtils.getPropertyNameConvention([k: null]) == null + } + + void 'getPropertyNameConvention names arrays after their component type'() { + expect: + GrailsNameUtils.getPropertyNameConvention([1, 2] as int[]) == 'intArray' + GrailsNameUtils.getPropertyNameConvention(['a'] as String[], 'X') == 'stringXArray' + } + + @Unroll + void 'getPropertyForGetter(#getter, #returnType) is #expected'() { + expect: + GrailsNameUtils.getPropertyForGetter(getter, returnType) == expected + + where: + getter | returnType | expected + 'getFoo' | 'String' | 'foo' + 'getURL' | 'String' | 'URL' + 'getA' | 'String' | 'a' + 'getaName' | 'String' | 'aName' + 'get_x' | 'String' | '_x' + 'get' | 'String' | null + 'getfoo' | 'String' | null + 'get1' | 'String' | null + 'getx' | 'String' | null + 'isActive' | 'boolean' | 'active' + 'isActive' | 'String' | null + 'foo' | 'String' | null + '' | 'String' | null + null | 'String' | null + } + + void 'getPropertyForGetter defaults to a boolean return type'() { + expect: + GrailsNameUtils.getPropertyForGetter('isActive') == 'active' + GrailsNameUtils.getPropertyForGetter('getName') == 'name' + GrailsNameUtils.getPropertyForGetter('isActive', String) == null + GrailsNameUtils.getPropertyForGetter('isActive', boolean) == 'active' + } + + @Unroll + void 'isGetter(#name, #returnType, #args) is #expected'() { + expect: + GrailsNameUtils.isGetter(name, returnType, args as Class[]) == expected + + where: + name | returnType | args | expected + 'getFoo' | String | [] | true + 'getFoo' | String | [String] | false + 'getX' | String | [] | true + 'getaProp' | String | [] | true + 'getx' | String | [] | false + 'get2other' | String | [] | false + 'get' | String | [] | false + 'isActive' | boolean | [] | true + 'isActive' | String | [] | false + 'isactive' | boolean | [] | false + 'foo' | String | [] | false + '' | String | [] | false + null | String | [] | false + 'getFoo' | String | null | false + } + + void 'the deprecated isGetter overload assumes a boolean return type'() { + expect: + GrailsNameUtils.isGetter('isActive', [] as Class[]) + GrailsNameUtils.isGetter('getActive', [] as Class[]) + !GrailsNameUtils.isGetter('getActive', [String] as Class[]) + } + + @Unroll + void 'getPropertyForSetter(#setter) is #expected'() { + expect: + GrailsNameUtils.getPropertyForSetter(setter) == expected + + where: + setter | expected + 'setFoo' | 'foo' + 'setURL' | 'URL' + 'set' | null + 'setfoo' | null + 'getFoo' | null + '' | null + null | null + } + + @Unroll + void 'convertValidPropertyMethodSuffix(#suffix) is #expected'() { + expect: + GrailsNameUtils.convertValidPropertyMethodSuffix(suffix) == expected + + where: + suffix | expected + '' | null + '1a' | null + 'A' | 'a' + 'a' | null + 'Name' | 'name' + 'aName' | 'aName' + '_x' | '_x' + 'name' | null + } + +} diff --git a/grails-gradle/model/src/test/groovy/grails/util/HolderSpec.groovy b/grails-gradle/model/src/test/groovy/grails/util/HolderSpec.groovy new file mode 100644 index 00000000000..44879f22589 --- /dev/null +++ b/grails-gradle/model/src/test/groovy/grails/util/HolderSpec.groovy @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package grails.util + +import spock.lang.Specification + +class HolderSpec extends Specification { + + void 'get returns null before anything is set'() { + given: + Holder holder = new Holder<>('test') + + expect: + holder.get() == null + } + + void 'set then get returns the stored value'() { + given: + Holder holder = new Holder<>('test') + + when: + holder.set('hello') + + then: + holder.get() == 'hello' + } + + void 'set with null clears the value'() { + given: + Holder holder = new Holder<>('test') + holder.set('hello') + + when: + holder.set(null) + + then: + holder.get() == null + } + + void 'get(mappedOnly=true) does not fall back to the singleton value when unmapped'() { + given: + Holder holder = new Holder<>('test') + holder.set('hello') + + expect: + holder.get(true) == 'hello' + } + + void 'a secondary lookup is consulted before the singleton'() { + given: + Holder holder = new Holder('secondary') { + @Override + protected String lookupSecondary() { + 'from-secondary' + } + } + holder.set('stored') + + expect: + holder.get() == 'stored' + + when: + holder.set(null) + + then: + holder.get() == 'from-secondary' + holder.get(true) == 'from-secondary' + } + + void 'values are keyed by the current class loader of the holder'() { + given: + Holder first = new Holder<>('first') + Holder second = new Holder<>('second') + + when: + first.set('one') + + then: + first.get() == 'one' + second.get() == null + } + +} diff --git a/grails-gradle/model/src/test/groovy/grails/util/SupplierUtilSpec.groovy b/grails-gradle/model/src/test/groovy/grails/util/SupplierUtilSpec.groovy new file mode 100644 index 00000000000..95ca994c4d7 --- /dev/null +++ b/grails-gradle/model/src/test/groovy/grails/util/SupplierUtilSpec.groovy @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package grails.util + +import java.util.function.Supplier + +import spock.lang.Specification + +class SupplierUtilSpec extends Specification { + + void 'memoized only invokes the delegate supplier once'() { + given: + int callCount = 0 + Supplier actual = { -> callCount++; 'value' } as Supplier + Supplier memoized = SupplierUtil.memoized(actual) + + when: + String first = memoized.get() + String second = memoized.get() + + then: + first == 'value' + second == 'value' + callCount == 1 + } + + void 'memoizedNonEmpty caches a non-null, non-empty value'() { + given: + int callCount = 0 + Supplier actual = { -> callCount++; 'value' } as Supplier + Supplier memoized = SupplierUtil.memoizedNonEmpty(actual) + + when: + String first = memoized.get() + String second = memoized.get() + + then: + first == 'value' + second == 'value' + callCount == 1 + } + + void 'memoizedNonEmpty does not cache a null value'() { + given: + int callCount = 0 + Supplier actual = { -> callCount++; null } as Supplier + Supplier memoized = SupplierUtil.memoizedNonEmpty(actual) + + when: + memoized.get() + memoized.get() + + then: + callCount == 2 + } + + void 'memoizedNonEmpty does not cache an empty Optional'() { + given: + int callCount = 0 + Supplier> actual = { -> callCount++; Optional.empty() } as Supplier> + Supplier> memoized = SupplierUtil.memoizedNonEmpty(actual) + + when: + memoized.get() + memoized.get() + + then: + callCount == 2 + } + + void 'memoizedNonEmpty caches a present Optional'() { + given: + int callCount = 0 + Supplier> actual = { -> callCount++; Optional.of('value') } as Supplier> + Supplier> memoized = SupplierUtil.memoizedNonEmpty(actual) + + when: + Optional first = memoized.get() + Optional second = memoized.get() + + then: + first.get() == 'value' + second.get() == 'value' + callCount == 1 + } + +} diff --git a/grails-gradle/model/src/test/groovy/org/grails/JavaCallerPin.java b/grails-gradle/model/src/test/groovy/org/grails/JavaCallerPin.java new file mode 100644 index 00000000000..e2eed95f507 --- /dev/null +++ b/grails-gradle/model/src/test/groovy/org/grails/JavaCallerPin.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.grails; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.util.regex.Pattern; + +import org.grails.build.parsing.CommandLine; +import org.grails.build.parsing.DefaultCommandLine; +import org.grails.io.support.ClassPathResource; +import org.grails.io.support.FileSystemResource; +import org.grails.io.support.GrailsResourceUtils; +import org.grails.io.support.Resource; +import org.grails.io.support.SpringIOUtils; +import org.grails.io.support.UrlResource; + +/** + * Compiled together with the Groovy sources, so the Java-facing API is pinned at compile time: a constant or a public + * field that stops being a field, a constructor that disappears, or a checked exception that is no longer declared + * (which makes the {@code catch} below unreachable) fails the build of this class. + */ +final class JavaCallerPin { + + static final String CLASS_EXTENSION = GrailsResourceUtils.CLASS_EXTENSION; + + static final int BUFFER_SIZE = SpringIOUtils.BUFFER_SIZE; + + static final String VERBOSE_ARGUMENT = CommandLine.VERBOSE_ARGUMENT; + + private JavaCallerPin() { + } + + static Pattern[] mutablePatterns() { + GrailsResourceUtils.DOMAIN_PATH_PATTERN = GrailsResourceUtils.DOMAIN_PATH_PATTERN; + return new Pattern[] { + GrailsResourceUtils.RESOURCE_PATH_PATTERN, + GrailsResourceUtils.SPRING_SCRIPTS_PATH_PATTERN, + GrailsResourceUtils.COMPILER_ROOT_PATTERNS[0] + }; + } + + static CommandLine commandLine() { + return new DefaultCommandLine(); + } + + static Resource[] resources() { + return new Resource[] {new ClassPathResource("a"), new FileSystemResource("a")}; + } + + static boolean urlResource() { + try { + return new UrlResource("file:a") != null; + } + catch (MalformedURLException e) { + return false; + } + } + + static boolean read(Resource resource, File in, File out) { + try (InputStream stream = resource.getInputStream()) { + return SpringIOUtils.copy(in, out) >= 0 && stream != null; + } + catch (IOException e) { + return false; + } + } + +} diff --git a/grails-gradle/model/src/test/groovy/org/grails/build/parsing/CommandLineParserSpec.groovy b/grails-gradle/model/src/test/groovy/org/grails/build/parsing/CommandLineParserSpec.groovy index e4a52ea0940..1eb14b0a8e8 100644 --- a/grails-gradle/model/src/test/groovy/org/grails/build/parsing/CommandLineParserSpec.groovy +++ b/grails-gradle/model/src/test/groovy/org/grails/build/parsing/CommandLineParserSpec.groovy @@ -406,4 +406,98 @@ class CommandLineParserSpec extends Specification { then: thrown ParseException } + + void 'translateCommandline returns no tokens for a null or empty string'() { + expect: + CommandLineParser.translateCommandline(null).length == 0 + CommandLineParser.translateCommandline('').length == 0 + } + + void 'translateCommandline keeps empty quoted tokens and collapses repeated spaces'() { + expect: + CommandLineParser.translateCommandline('a b') == ['a', 'b'] as String[] + CommandLineParser.translateCommandline("a '' b") == ['a', '', 'b'] as String[] + CommandLineParser.translateCommandline('a "x y" b') == ['a', 'x y', 'b'] as String[] + CommandLineParser.translateCommandline("""a "it's" b""") == ['a', "it's", 'b'] as String[] + } + + void 'the most recently created command line is the current one'() { + when: + CommandLine parsed = new CommandLineParser().parse('run-app') + + then: + CommandLineParser.currentCommandLine.is(parsed) + } + + void 'parseString with a command name treats every token as an argument'() { + when: + CommandLine commandLine = new CommandLineParser().parseString('run-app', 'dev --flag value') + + then: + commandLine.commandName == 'run-app' + commandLine.remainingArgs == ['dev'] + commandLine.optionValue('flag') == 'value' + } + + void 'declared options are recognised with and without a value'() { + given: + CommandLineParser parser = new CommandLineParser() + parser.addOption('verbose', 'Verbose output') + parser.addOption('level', 'The level') + + when: + CommandLine commandLine = parser.parse('run-app', '--verbose', '-level=3') + + then: + commandLine.hasOption('verbose') + commandLine.optionValue('verbose') == true + commandLine.optionValue('level') == '3' + commandLine.undeclaredOptions.isEmpty() + } + + void 'a bare dash and null or blank arguments are ignored'() { + when: + CommandLine commandLine = new CommandLineParser().parse([null, ' ', '-', 'run-app', '', 'arg'] as String[]) + + then: + commandLine.commandName == 'run-app' + commandLine.remainingArgs == ['arg'] + commandLine.undeclaredOptions.isEmpty() + } + + void 'surrounding double quotes are removed from an argument'() { + when: + CommandLine commandLine = new CommandLineParser().parse('run-app', '"quoted"') + + then: + commandLine.remainingArgs == ['quoted'] + } + + void 'environment shortcuts only apply to the first argument position'() { + when: + CommandLine commandLine = new CommandLineParser().parse('prod', 'run-app', 'dev') + + then: + commandLine.environment == 'production' + commandLine.commandName == 'run-app' + commandLine.remainingArgs == ['dev'] + } + + void 'an option name containing a space is rejected whether or not it has a value'() { + when: + new CommandLineParser().parse('run-app', '-bad name=value') + + then: + thrown(ParseException) + } + + void 'ParseException keeps its message or cause'() { + given: + Throwable cause = new IllegalStateException('boom') + + expect: + new ParseException('msg').message == 'msg' + new ParseException(cause).cause.is(cause) + } + } diff --git a/grails-gradle/model/src/test/groovy/org/grails/build/parsing/DefaultCommandLineSpec.groovy b/grails-gradle/model/src/test/groovy/org/grails/build/parsing/DefaultCommandLineSpec.groovy new file mode 100644 index 00000000000..b60fa0faab7 --- /dev/null +++ b/grails-gradle/model/src/test/groovy/org/grails/build/parsing/DefaultCommandLineSpec.groovy @@ -0,0 +1,276 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.grails.build.parsing + +import spock.lang.Specification + +import grails.util.Environment + +class DefaultCommandLineSpec extends Specification { + + private String originalEnv + private String originalDefault + + void setup() { + originalEnv = System.getProperty(Environment.KEY) + originalDefault = System.getProperty(Environment.DEFAULT) + System.clearProperty(Environment.KEY) + System.clearProperty(Environment.DEFAULT) + } + + void cleanup() { + restore(Environment.KEY, originalEnv) + restore(Environment.DEFAULT, originalDefault) + } + + private static void restore(String key, String value) { + if (value == null) { + System.clearProperty(key) + } + else { + System.setProperty(key, value) + } + } + + void 'a new command line has no command, arguments or options'() { + given: + DefaultCommandLine commandLine = new DefaultCommandLine() + + expect: + commandLine.commandName == null + commandLine.remainingArgs == [] + commandLine.remainingArgsArray.length == 0 + commandLine.remainingArgsString == '' + commandLine.undeclaredOptions == [:] + commandLine.systemProperties.isEmpty() + commandLine.rawArguments == null + !commandLine.environmentSet + !commandLine.hasOption('anything') + commandLine.optionValue('anything') == null + commandLine.lastOption() == null + } + + void 'the environment defaults to development and is flagged as defaulted'() { + given: + DefaultCommandLine commandLine = new DefaultCommandLine() + + expect: + commandLine.environment == Environment.DEVELOPMENT.name + System.getProperty(Environment.KEY) == Environment.DEVELOPMENT.name + System.getProperty(Environment.DEFAULT) == 'true' + } + + void 'the default environment honours an already set system property'() { + given: + System.setProperty(Environment.KEY, 'custom') + + expect: + new DefaultCommandLine().environment == 'custom' + } + + void 'an explicit environment wins and is published as a system property'() { + given: + DefaultCommandLine commandLine = new DefaultCommandLine() + + when: + commandLine.setEnvironment('production') + + then: + commandLine.environmentSet + commandLine.environment == 'production' + System.getProperty(Environment.KEY) == 'production' + System.getProperty(Environment.DEFAULT) == 'false' + } + + void 'the environment for a command comes from the command defaults'() { + given: + DefaultCommandLine commandLine = new DefaultCommandLine() + commandLine.setCommand(command) + + expect: + commandLine.environment == expected + commandLine.lookupEnvironmentForCommand() == expected + + where: + command | expected + 'war' | 'production' + 'test-app' | 'test' + 'run-app' | 'development' + } + + void 'a command without a default environment falls back to the system property'() { + given: + System.setProperty(Environment.KEY, 'custom') + DefaultCommandLine commandLine = new DefaultCommandLine() + commandLine.setCommandName('run-app') + + expect: + commandLine.lookupEnvironmentForCommand() == 'custom' + commandLine.environment == 'custom' + } + + void 'setCommandName records the command, and refresh-dependencies also becomes an option'() { + given: + DefaultCommandLine commandLine = new DefaultCommandLine() + + when: + commandLine.setCommandName('run-app') + + then: + commandLine.commandName == 'run-app' + !commandLine.hasOption(CommandLine.REFRESH_DEPENDENCIES_ARGUMENT) + + when: + commandLine.setCommandName(CommandLine.REFRESH_DEPENDENCIES_ARGUMENT) + + then: + commandLine.commandName == CommandLine.REFRESH_DEPENDENCIES_ARGUMENT + commandLine.hasOption(CommandLine.REFRESH_DEPENDENCIES_ARGUMENT) + commandLine.optionValue(CommandLine.REFRESH_DEPENDENCIES_ARGUMENT) == true + } + + void 'declared options report their value, defaulting to true'() { + given: + DefaultCommandLine commandLine = new DefaultCommandLine() + commandLine.addDeclaredOption('flag', new Option('flag', 'a flag')) + commandLine.addDeclaredOption('level', new Option('level', 'a level'), '3') + + expect: + commandLine.hasOption('flag') + commandLine.optionValue('flag') == true + commandLine.optionValue('level') == '3' + commandLine.declaredOptions.level.option.name == 'level' + commandLine.declaredOptions.level.value == '3' + } + + void 'undeclared options report their value, defaulting to true'() { + given: + DefaultCommandLine commandLine = new DefaultCommandLine() + commandLine.addUndeclaredOption('flag') + commandLine.addUndeclaredOption('name', 'value') + + expect: + commandLine.hasOption('flag') + commandLine.optionValue('flag') == true + commandLine.optionValue('name') == 'value' + commandLine.undeclaredOptions == [flag: true, name: 'value'] + } + + void 'declared options take precedence over undeclared ones of the same name'() { + given: + DefaultCommandLine commandLine = new DefaultCommandLine() + commandLine.addUndeclaredOption('mode', 'undeclared') + commandLine.addDeclaredOption('mode', new Option('mode', 'the mode'), 'declared') + + expect: + commandLine.optionValue('mode') == 'declared' + } + + void 'lastOption returns the most recently added undeclared option'() { + given: + DefaultCommandLine commandLine = new DefaultCommandLine() + commandLine.addUndeclaredOption('first') + commandLine.addUndeclaredOption('second', 'x') + + expect: + commandLine.lastOption().key == 'second' + commandLine.lastOption().value == 'x' + } + + void 'remaining arguments are exposed as a list, array and strings'() { + given: + DefaultCommandLine commandLine = new DefaultCommandLine() + commandLine.addRemainingArg('one') + commandLine.addRemainingArg('two') + + expect: + commandLine.remainingArgs == ['one', 'two'] + commandLine.remainingArgsArray == ['one', 'two'] as String[] + commandLine.remainingArgsString == 'one two' + commandLine.remainingArgsLineSeparated == 'one\ntwo' + commandLine.remainingArgsWithOptionsString == 'one two' + } + + void 'the string with options renders flags and valued options'() { + given: + DefaultCommandLine commandLine = new DefaultCommandLine() + commandLine.addRemainingArg('arg') + commandLine.addUndeclaredOption('flag') + commandLine.addUndeclaredOption('name', 'value') + commandLine.addUndeclaredOption('off', Boolean.FALSE) + + expect: + commandLine.remainingArgsWithOptionsString == 'arg -flag -name=value -off=false' + commandLine.remainingArgsString == 'arg' + } + + void 'system properties are stored, and grails.env also selects the environment'() { + given: + DefaultCommandLine commandLine = new DefaultCommandLine() + + when: + commandLine.addSystemProperty('foo', 'bar') + + then: + commandLine.systemProperties.getProperty('foo') == 'bar' + !commandLine.environmentSet + + when: + commandLine.addSystemProperty(Environment.KEY, 'test') + + then: + commandLine.systemProperties.getProperty(Environment.KEY) == 'test' + commandLine.environmentSet + commandLine.environment == 'test' + } + + void 'raw arguments are kept as given'() { + given: + DefaultCommandLine commandLine = new DefaultCommandLine() + String[] args = ['a', '-b'] as String[] + + when: + commandLine.setRawArguments(args) + + then: + commandLine.rawArguments.is(args) + } + + void 'parseNew carries the existing state into a freshly parsed command line'() { + given: + DefaultCommandLine commandLine = new DefaultCommandLine() + commandLine.addSystemProperty('foo', 'bar') + commandLine.addUndeclaredOption('existing', 'yes') + commandLine.addDeclaredOption('declared', new Option('declared', 'd')) + + when: + CommandLine parsed = commandLine.parseNew(['run-app', '-more', 'arg'] as String[]) + + then: + !parsed.is(commandLine) + parsed.commandName == 'run-app' + parsed.remainingArgs == [] + parsed.systemProperties.getProperty('foo') == 'bar' + parsed.optionValue('existing') == 'yes' + parsed.optionValue('declared') == true + parsed.optionValue('more') == 'arg' + !commandLine.hasOption('more') + } + +} diff --git a/grails-gradle/model/src/test/groovy/org/grails/build/parsing/OptionSpec.groovy b/grails-gradle/model/src/test/groovy/org/grails/build/parsing/OptionSpec.groovy new file mode 100644 index 00000000000..7c0f0547d4f --- /dev/null +++ b/grails-gradle/model/src/test/groovy/org/grails/build/parsing/OptionSpec.groovy @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.grails.build.parsing + +import spock.lang.Specification + +class OptionSpec extends Specification { + + void 'stores the given name and description'() { + when: + Option option = new Option('offline', 'Run offline') + + then: + option.name == 'offline' + option.description == 'Run offline' + } + + void 'defaults a null description to an empty string'() { + when: + Option option = new Option('offline', null) + + then: + option.description == '' + } + + void 'rejects a null name'() { + when: + new Option(null, 'desc') + + then: + thrown(IllegalArgumentException) + } + + void 'rejects an empty name'() { + when: + new Option('', 'desc') + + then: + thrown(IllegalArgumentException) + } + +} diff --git a/grails-gradle/model/src/test/groovy/org/grails/io/support/AbstractFileResolvingResourceSpec.groovy b/grails-gradle/model/src/test/groovy/org/grails/io/support/AbstractFileResolvingResourceSpec.groovy new file mode 100644 index 00000000000..9b7588e0866 --- /dev/null +++ b/grails-gradle/model/src/test/groovy/org/grails/io/support/AbstractFileResolvingResourceSpec.groovy @@ -0,0 +1,197 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.grails.io.support + +import java.util.jar.JarEntry +import java.util.jar.JarOutputStream + +import com.sun.net.httpserver.HttpExchange +import com.sun.net.httpserver.HttpServer +import spock.lang.Shared +import spock.lang.Specification +import spock.lang.TempDir + +class AbstractFileResolvingResourceSpec extends Specification { + + @TempDir + File dir + + @Shared + HttpServer server + + @Shared + int port + + void setupSpec() { + server = HttpServer.create(new InetSocketAddress(InetAddress.loopbackAddress, 0), 0) + server.createContext('/ok') { HttpExchange exchange -> + exchange.responseHeaders.add('Last-Modified', 'Thu, 01 Jan 2015 00:00:00 GMT') + if (exchange.requestMethod == 'HEAD') { + exchange.responseHeaders.add('Content-Length', '5') + exchange.sendResponseHeaders(200, -1) + } + else { + exchange.sendResponseHeaders(200, 5) + exchange.responseBody.write('hello'.bytes) + } + exchange.close() + } + server.createContext('/missing') { HttpExchange exchange -> + exchange.sendResponseHeaders(404, -1) + exchange.close() + } + server.createContext('/error') { HttpExchange exchange -> + exchange.sendResponseHeaders(500, -1) + exchange.close() + } + server.start() + port = server.address.port + } + + void cleanupSpec() { + server.stop(0) + } + + private UrlResource http(String path) { + new UrlResource("http://${InetAddress.loopbackAddress.hostAddress}:${port}${path}".toString()) + } + + private File fileWith(String name, String content) { + File file = new File(dir, name) + file.text = content + file + } + + void 'a file resource resolves, reads and measures the underlying file'() { + given: + File file = fileWith('data.txt', 'hello') + UrlResource resource = new UrlResource(file.toURI().toURL()) + + expect: + resource.exists() + resource.readable + resource.file == file + resource.contentLength() == 5 + resource.lastModified() == file.lastModified() + } + + void 'a missing file is neither existing nor readable'() { + given: + UrlResource resource = new UrlResource(new File(dir, 'nope.txt').toURI().toURL()) + + expect: + !resource.exists() + !resource.readable + } + + void 'a directory exists but is not readable'() { + given: + UrlResource resource = new UrlResource(dir.toURI().toURL()) + + expect: + resource.exists() + !resource.readable + } + + void 'a resource outside the file system cannot be resolved to a file'() { + when: + new UrlResource('http://localhost/x').file + + then: + FileNotFoundException e = thrown() + e.message.contains('does not reside in the file system') + } + + void 'readable swallows the failure to resolve a file for a class path resource that does not exist'() { + expect: + !new ClassPathResource('no/such/resource.txt').readable + !new ClassPathResource('no/such/resource.txt').exists() + } + + void 'exists reflects the status of an http resource'() { + expect: + http('/ok').exists() + !http('/missing').exists() + !http('/error').exists() + } + + void 'an unreachable http resource does not exist'() { + given: + int unusedPort + new ServerSocket(0).withCloseable { unusedPort = it.localPort } + + expect: + !new UrlResource("http://${InetAddress.loopbackAddress.hostAddress}:${unusedPort}/x".toString()).exists() + } + + void 'an http resource is readable'() { + expect: + http('/ok').readable + } + + void 'contentLength and lastModified come from the http response headers'() { + expect: + http('/ok').contentLength() == 5 + http('/ok').lastModified() == 1420070400000L + } + + void 'a resource inside a jar reports a length and exists'() { + given: + File jar = new File(dir, 'sample.jar') + new JarOutputStream(new FileOutputStream(jar)).withCloseable { JarOutputStream out -> + out.putNextEntry(new JarEntry('inside.txt')) + out.write('jar content'.bytes) + out.closeEntry() + } + UrlResource resource = new UrlResource("jar:${jar.toURI()}!/inside.txt".toString()) + + expect: + resource.exists() + resource.readable + resource.contentLength() == 11 + resource.inputStream.text == 'jar content' + resource.getFileForLastModifiedCheck() == jar + } + + void 'the file used for the last modified check is the file itself for a file resource'() { + given: + File file = fileWith('plain.txt', 'x') + + expect: + new UrlResource(file.toURI().toURL()).getFileForLastModifiedCheck() == file + } + + void 'a class path resource inside a jar is read through the jar and not as a file'() { + given: + ClassPathResource resource = new ClassPathResource('spock/lang/Specification.class') + + expect: + resource.exists() + resource.readable + resource.contentLength() > 0 + resource.getFileForLastModifiedCheck().name.endsWith('.jar') + + when: + resource.file + + then: + thrown(FileNotFoundException) + } + +} diff --git a/grails-gradle/model/src/test/groovy/org/grails/io/support/DefaultResourceLoaderSpec.groovy b/grails-gradle/model/src/test/groovy/org/grails/io/support/DefaultResourceLoaderSpec.groovy new file mode 100644 index 00000000000..edee40efa2c --- /dev/null +++ b/grails-gradle/model/src/test/groovy/org/grails/io/support/DefaultResourceLoaderSpec.groovy @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.grails.io.support + +import spock.lang.Specification + +class DefaultResourceLoaderSpec extends Specification { + + void 'getResource resolves a classpath: pseudo-URL to a ClassPathResource'() { + given: + DefaultResourceLoader loader = new DefaultResourceLoader() + + when: + Resource resource = loader.getResource('classpath:org/grails/io/support/Resource.class') + + then: + resource instanceof ClassPathResource + ((ClassPathResource) resource).getPath() == 'org/grails/io/support/Resource.class' + } + + void 'getResource resolves a real URL to a UrlResource'() { + given: + DefaultResourceLoader loader = new DefaultResourceLoader() + + when: + Resource resource = loader.getResource('file:///tmp/foo.txt') + + then: + resource instanceof UrlResource + } + + void 'getResource falls back to a classpath-relative resource for a plain path'() { + given: + DefaultResourceLoader loader = new DefaultResourceLoader() + + when: + Resource resource = loader.getResource('org/grails/io/support/Resource.class') + + then: + resource instanceof ClassPathResource + resource.exists() + } + + void 'getClassLoader defaults to the thread context class loader'() { + expect: + new DefaultResourceLoader().getClassLoader() != null + } + + void 'a custom classloader passed to the constructor is used'() { + given: + ClassLoader custom = new URLClassLoader(new URL[0], null) + + expect: + new DefaultResourceLoader(custom).getClassLoader().is(custom) + } + + void 'setClassLoader overrides the classloader used for classpath resolution'() { + given: + DefaultResourceLoader loader = new DefaultResourceLoader() + ClassLoader custom = new URLClassLoader(new URL[0], null) + + when: + loader.setClassLoader(custom) + + then: + loader.getClassLoader().is(custom) + } + + void 'the default class loader falls back to this library when the thread has none'() { + given: + ClassLoader original = Thread.currentThread().contextClassLoader + + when: + Thread.currentThread().contextClassLoader = null + + then: + DefaultResourceLoader.defaultClassLoader.is(DefaultResourceLoader.classLoader) + + cleanup: + Thread.currentThread().contextClassLoader = original + } + + void 'a loader whose class loader was cleared uses the default class loader again'() { + given: + DefaultResourceLoader loader = new DefaultResourceLoader(new URLClassLoader(new URL[0])) + + when: + loader.classLoader = null + + then: + loader.classLoader.is(DefaultResourceLoader.defaultClassLoader) + } + + void 'plain path resources resolve relative to their own location'() { + given: + Resource resource = new DefaultResourceLoader().getResource('org/grails/io/support/ClassPathResourceSpec.class') + + when: + Resource sibling = resource.createRelative('DefaultResourceLoaderSpec.class') + + then: + resource.exists() + resource.pathWithinContext == 'org/grails/io/support/ClassPathResourceSpec.class' + sibling.exists() + sibling.filename == 'DefaultResourceLoaderSpec.class' + sibling.pathWithinContext == 'org/grails/io/support/DefaultResourceLoaderSpec.class' + } + +} diff --git a/grails-gradle/model/src/test/groovy/org/grails/io/support/GrailsResourceUtilsSpec.groovy b/grails-gradle/model/src/test/groovy/org/grails/io/support/GrailsResourceUtilsSpec.groovy index 62970a05523..c2de91f95cf 100644 --- a/grails-gradle/model/src/test/groovy/org/grails/io/support/GrailsResourceUtilsSpec.groovy +++ b/grails-gradle/model/src/test/groovy/org/grails/io/support/GrailsResourceUtilsSpec.groovy @@ -22,6 +22,7 @@ import groovy.util.ConfigObject import spock.lang.Specification import spock.lang.TempDir +import spock.lang.Unroll class GrailsResourceUtilsSpec extends Specification { @@ -259,4 +260,79 @@ class GrailsResourceUtilsSpec extends Specification { GrailsResourceUtils.instantiateFromFlatConfig([type: 42], 'type', 'java.util.ArrayList') instanceof ArrayList GrailsResourceUtils.instantiateFromFlatConfig([:], 'type', 'java.util.ArrayList') instanceof ArrayList } + + private static URL urlWithProtocol(String protocol, String spec) { + // "zip:"/"wsjar:" are not registered on a plain JVM - build the URL with a + // no-op handler so only the protocol name is exercised, not real resolution. + URLStreamHandler noopHandler = new URLStreamHandler() { + @Override + protected URLConnection openConnection(URL u) { + return null + } + } + return new URL(null, protocol + ':' + spec, noopHandler) + } + + void 'isJarURL is true for jar:/zip:/wsjar: protocols and false otherwise'() { + expect: + GrailsResourceUtils.isJarURL(new URL('jar:file:/foo.jar!/Bar.class')) + GrailsResourceUtils.isJarURL(urlWithProtocol('zip', 'file:/foo.jar!/Bar.class')) + GrailsResourceUtils.isJarURL(urlWithProtocol('wsjar', 'file:/foo.jar!/Bar.class')) + !GrailsResourceUtils.isJarURL(new URL('file:///tmp/foo.txt')) + } + + void 'COMPILER_ROOT_PATTERNS contains the spring scripts and resource path patterns'() { + expect: + GrailsResourceUtils.COMPILER_ROOT_PATTERNS.length == 2 + GrailsResourceUtils.COMPILER_ROOT_PATTERNS[1].matcher( + '/project/grails-app/controllers/com/example/BookController.groovy').find() + } + + void 'getFile falls back to the URL file when the URL is not a valid URI'() { + expect: + GrailsResourceUtils.getFile(new URL('file:/tmp/a|b.txt'), 'desc') == new File('/tmp/a|b.txt') + } + + void 'isFileURL also accepts the vfs family of protocols'() { + expect: + GrailsResourceUtils.isFileURL(urlWithProtocol('vfs', '/tmp/x')) + GrailsResourceUtils.isFileURL(urlWithProtocol('vfszip', '/tmp/x')) + !GrailsResourceUtils.isFileURL(urlWithProtocol('other', '/tmp/x')) + } + + void 'isJarURL accepts a code-source URL only when it points inside a jar'() { + expect: + GrailsResourceUtils.isJarURL(urlWithProtocol('code-source', '/x/y.jar!/z')) + !GrailsResourceUtils.isJarURL(urlWithProtocol('code-source', '/x/y')) + } + + @Unroll + void 'extractJarFileURL(#protocol:#spec) is #expected'() { + expect: + GrailsResourceUtils.extractJarFileURL(urlWithProtocol(protocol, spec)).toString() == expected + + where: + protocol | spec | expected + 'jar' | 'file:/tmp/a.jar!/x/y.txt' | 'file:/tmp/a.jar' + 'jar' | 'C:/mypath/myjar.jar!/x.txt' | 'file:/C:/mypath/myjar.jar' + 'jar' | '/mypath/myjar.jar!/x.txt' | 'file:/mypath/myjar.jar' + 'http' | '//example.com/a.jar' | 'http://example.com/a.jar' + } + + void 'getPathFromBaseDir makes other paths relative to the base directory when there is one'() { + given: + File baseDir = grails.util.BuildSettings.BASE_DIR + + expect: + baseDir == null ? GrailsResourceUtils.getPathFromBaseDir('x/y.txt') == null : + GrailsResourceUtils.getPathFromBaseDir(new File(baseDir, 'x/y.txt').path) == 'x/y.txt' + } + + void 'instantiateFromFlatConfig fails for an unknown class'() { + when: + GrailsResourceUtils.instantiateFromFlatConfig([k: 'no.such.Type'], 'k', 'java.util.HashMap') + + then: + thrown(ClassNotFoundException) + } } diff --git a/grails-gradle/model/src/test/groovy/org/grails/io/support/SpringIOUtilsSpec.groovy b/grails-gradle/model/src/test/groovy/org/grails/io/support/SpringIOUtilsSpec.groovy index c3afb02351b..ff2383300c7 100644 --- a/grails-gradle/model/src/test/groovy/org/grails/io/support/SpringIOUtilsSpec.groovy +++ b/grails-gradle/model/src/test/groovy/org/grails/io/support/SpringIOUtilsSpec.groovy @@ -57,6 +57,9 @@ class SpringIOUtilsSpec extends Specification { @TempDir Path tempDir + @TempDir + File tempFileDir + private String externalEntityDocument() { Path secret = tempDir.resolve('secret.txt') Files.writeString(secret, SECRET) @@ -124,6 +127,15 @@ class SpringIOUtilsSpec extends Specification { e.message.contains('DOCTYPE is disallowed') } + void 'newSAXParser returns a namespace-aware, non-validating parser'() { + when: + def parser = SpringIOUtils.newSAXParser() + + then: + parser.isNamespaceAware() + !parser.isValidating() + } + void 'asking for doctype tolerance parses a descriptor that declares one'() { when: def parsed = SpringIOUtils.createXmlSlurper(true).parseText(TLD) @@ -191,4 +203,260 @@ class SpringIOUtilsSpec extends Specification { where: allowDocType << [false, true] } + + void 'byteArrayToHexString converts bytes to lowercase hex'() { + expect: + SpringIOUtils.byteArrayToHexString([0x00, 0x0f, (byte) 0xff] as byte[]) == '000fff' + } + + void 'byteArrayToHexString returns null for a null or empty array'() { + expect: + SpringIOUtils.byteArrayToHexString(null) == null + SpringIOUtils.byteArrayToHexString(new byte[0]) == null + } + + void 'copy(byte[], File) writes the bytes to the file'() { + given: + File out = new File(tempFileDir, 'out.bin') + + when: + SpringIOUtils.copy('hello'.bytes, out) + + then: + out.text == 'hello' + } + + void 'copy(File, File) copies file contents'() { + given: + File source = new File(tempFileDir, 'source.txt') + source.text = 'copy me' + File target = new File(tempFileDir, 'target.txt') + + when: + int count = SpringIOUtils.copy(source, target) + + then: + target.text == 'copy me' + count == 'copy me'.bytes.length + } + + void 'copyToByteArray(File) reads the whole file'() { + given: + File source = new File(tempFileDir, 'source.txt') + source.text = 'bytes here' + + expect: + new String(SpringIOUtils.copyToByteArray(source)) == 'bytes here' + } + + void 'copyToString(Reader) reads the whole reader'() { + expect: + SpringIOUtils.copyToString(new StringReader('reader contents')) == 'reader contents' + } + + void 'copy(String, Writer) writes the string and closes the writer'() { + given: + StringWriter writer = new StringWriter() + + when: + SpringIOUtils.copy('written text', writer) + + then: + writer.toString() == 'written text' + } + + void 'computeChecksum computes an md5 digest'() { + given: + File source = new File(tempFileDir, 'checksum.txt') + source.text = 'checksum me' + + expect: + SpringIOUtils.computeChecksum(source, 'md5').length() == 32 + } + + void 'computeChecksum rejects an unknown algorithm'() { + given: + File source = new File(tempFileDir, 'checksum.txt') + source.text = 'checksum me' + + when: + SpringIOUtils.computeChecksum(source, 'not-an-algorithm') + + then: + thrown(IllegalArgumentException) + } + + void 'closeQuietly closes without throwing'() { + given: + Closeable closeable = { -> } as Closeable + + expect: + SpringIOUtils.closeQuietly(closeable) + SpringIOUtils.closeQuietly(null) + } + + void 'closeQuietly swallows an IOException from close'() { + given: + Closeable closeable = { -> throw new IOException('boom') } as Closeable + + when: + SpringIOUtils.closeQuietly(closeable) + + then: + noExceptionThrown() + } + + void 'addAll concatenates two arrays preserving order'() { + expect: + SpringIOUtils.addAll(['a', 'b'] as String[], ['c', 'd'] as String[]) == ['a', 'b', 'c', 'd'] + } + + void 'addAll rejects an array whose type cannot hold the other arrays elements'() { + when: + SpringIOUtils.addAll(['a'] as String[], [1] as Integer[]) + + then: + IllegalArgumentException e = thrown() + e.message == 'Cannot store java.lang.Integer in an array of java.lang.String' + } + + void 'copyAll copies every resource below the base into the target directory'() { + given: + File source = new File(tempFileDir, 'src') + source.mkdirs() + new File(source, 'a.txt').text = 'A' + new File(source, 'b.txt').text = 'B' + File target = new File(tempFileDir, 'target') + target.mkdirs() + + when: + SpringIOUtils.copyAll(new FileSystemResource(source), [new FileSystemResource(new File(source, 'a.txt')), + new FileSystemResource(new File(source, 'b.txt'))] as Resource[], target) + + then: + new File(target, 'a.txt').text == 'A' + new File(target, 'b.txt').text == 'B' + } + + void 'copy(Resource, File) writes the resource to the file and reports the size'() { + given: + File in = new File(tempFileDir, 'in.txt') + in.text = 'resource' + File out = new File(tempFileDir, 'out.txt') + + expect: + SpringIOUtils.copy(new FileSystemResource(in), out) == 8 + out.text == 'resource' + } + + void 'copy(File, File) reports the number of bytes copied'() { + given: + File in = new File(tempFileDir, 'in2.txt') + in.text = 'twelve bytes' + File out = new File(tempFileDir, 'out2.txt') + + expect: + SpringIOUtils.copy(in, out) == 12 + } + + void 'copy(InputStream, OutputStream) copies, flushes and closes both streams'() { + given: + boolean inClosed = false + boolean outClosed = false + InputStream input = new ByteArrayInputStream('payload'.bytes) { + @Override + void close() { + inClosed = true + } + } + ByteArrayOutputStream output = new ByteArrayOutputStream() { + @Override + void close() { + outClosed = true + } + } + + expect: + SpringIOUtils.copy(input, output) == 7 + output.toString() == 'payload' + inClosed + outClosed + } + + void 'copy(InputStream, OutputStream) still closes the streams when they fail to close'() { + given: + InputStream input = new ByteArrayInputStream('x'.bytes) { + @Override + void close() { + throw new IOException('in') + } + } + OutputStream output = new ByteArrayOutputStream() { + @Override + void close() { + throw new IOException('out') + } + } + + expect: + SpringIOUtils.copy(input, output) == 1 + } + + void 'copy(byte[], OutputStream) writes and closes the stream even when closing fails'() { + given: + ByteArrayOutputStream output = new ByteArrayOutputStream() { + @Override + void close() { + throw new IOException('out') + } + } + + when: + SpringIOUtils.copy('bytes'.bytes, (OutputStream) output) + + then: + output.toString() == 'bytes' + } + + void 'copyToByteArray(InputStream) reads everything and closes the stream'() { + expect: + SpringIOUtils.copyToByteArray(new ByteArrayInputStream('abc'.bytes)) == 'abc'.bytes + } + + void 'copy(Reader, Writer) copies characters and closes both'() { + given: + StringWriter writer = new StringWriter() + + expect: + SpringIOUtils.copy(new StringReader('chars'), writer) == 5 + writer.toString() == 'chars' + } + + void 'copy methods refuse a missing argument with a NullPointerException naming it: #message'() { + when: + action() + + then: + NullPointerException e = thrown() + e.message == message + + where: + message | action + 'No input File specified' | { SpringIOUtils.copy((File) null, new File('x')) } + 'No output File specified' | { SpringIOUtils.copy(new File('x'), (File) null) } + 'No input File specified' | { SpringIOUtils.copy((Resource) null, new File('x')) } + 'No output File specified' | { SpringIOUtils.copy(new FileSystemResource('x'), (File) null) } + 'No input byte array specified' | { SpringIOUtils.copy((byte[]) null, new File('x')) } + 'No output File specified' | { SpringIOUtils.copy('x'.bytes, (File) null) } + 'No input File specified' | { SpringIOUtils.copyToByteArray((File) null) } + 'No input stream specified' | { SpringIOUtils.copy((InputStream) null, new ByteArrayOutputStream()) } + 'No output stream specified' | { SpringIOUtils.copy(new ByteArrayInputStream(new byte[0]), (OutputStream) null) } + 'No input byte array specified' | { SpringIOUtils.copy((byte[]) null, (OutputStream) new ByteArrayOutputStream()) } + 'No output stream specified' | { SpringIOUtils.copy('x'.bytes, (OutputStream) null) } + 'No input Reader specified' | { SpringIOUtils.copy((Reader) null, new StringWriter()) } + 'No output Writer specified' | { SpringIOUtils.copy(new StringReader('x'), (Writer) null) } + 'No input String specified' | { SpringIOUtils.copy((String) null, new StringWriter()) } + 'No output Writer specified' | { SpringIOUtils.copy('x', (Writer) null) } + } + } diff --git a/grails-gradle/model/src/test/groovy/org/grails/io/support/UrlResourceSpec.groovy b/grails-gradle/model/src/test/groovy/org/grails/io/support/UrlResourceSpec.groovy index ef06a5efb0d..0d2250413bd 100644 --- a/grails-gradle/model/src/test/groovy/org/grails/io/support/UrlResourceSpec.groovy +++ b/grails-gradle/model/src/test/groovy/org/grails/io/support/UrlResourceSpec.groovy @@ -133,4 +133,13 @@ class UrlResourceSpec extends Specification { then: thrown(MalformedURLException) } + + void 'createRelative drops a leading slash and returns null for an unusable path'() { + given: + UrlResource base = new UrlResource('http://example.com/a/b.txt') + + expect: + base.createRelative('/c.txt').URL.toString() == 'http://example.com/a/c.txt' + base.createRelative('foo:bar') == null + } }