Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@

// See gradle/common.gradle for more or less global build logic applied to the subprojects

buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'ru.vyarus:gradle-animalsniffer-plugin:2.0.1'
}
}

plugins {
id 'idea'
}
Expand Down
19 changes: 19 additions & 0 deletions gradle/common.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,25 @@ java {
targetCompatibility = JavaVersion.VERSION_17
}

// Same Android-suitability check as gestalt's gestalt-library-common.gradle.kts: verifies this
// module's API surface only uses classes/methods that also exist on Android, so NUI stays usable
// from an eventual Android-facing module without discovering incompatible JDK-only API usage late.
apply plugin: 'ru.vyarus.animalsniffer'

dependencies {
signature 'com.toasttab.android:gummy-bears-api-24:0.15.0:coreLib2@signature'
}

animalsniffer {
// java.nio.* APIs can be desugared by D8. java.io.File.toPath() also needs to be excluded.
//
// java.awt.Toolkit/datatransfer.* genuinely don't exist on Android at all - UIText.AwtClipboard
// isolates that usage behind a LinkageError-catching caller so it degrades gracefully instead
// of crashing, but the check itself can't be satisfied for a real platform absence like this
// one, so it's excluded here rather than by leaving the whole check non-fatal.
ignore = ['java.nio.file.*', 'java.io.File', 'java.awt.Toolkit', 'java.awt.datatransfer.*']
}

// We use both Maven Central and our own Artifactory instance, which contains module builds, extra libs, and so on
repositories {
// For development so you can publish binaries locally and have them grabbed from there
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.reflections.ReflectionUtils;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
Expand Down Expand Up @@ -516,7 +517,27 @@ public static String resolvedMethodToString(Type declaringType,
try {
StringBuilder stringBuilder = new StringBuilder();

TypeVariable[] typeParameters = method.getTypeParameters();
// Executable itself isn't available before Android API 26. Method and Constructor each
// declare getTypeParameters()/getGenericParameterTypes()/isVarArgs() directly (and did
// so long before Executable existed), so resolve them through the concrete type instead
// of calling through the Executable-typed parameter - a call resolved against Executable
// trips AnimalSniffer even though the underlying Method/Constructor method is fine.
TypeVariable[] typeParameters;
Type[] unresolvedParameterTypes;
boolean varArgs;

if (method instanceof Method) {
Method asMethod = (Method) method;
typeParameters = asMethod.getTypeParameters();
unresolvedParameterTypes = asMethod.getGenericParameterTypes();
varArgs = asMethod.isVarArgs();
} else {
Constructor<?> asConstructor = (Constructor<?>) method;
typeParameters = asConstructor.getTypeParameters();
unresolvedParameterTypes = asConstructor.getGenericParameterTypes();
varArgs = asConstructor.isVarArgs();
}

if (typeParameters.length > 0) {
boolean first = true;
stringBuilder.append('<');
Expand All @@ -534,24 +555,24 @@ public static String resolvedMethodToString(Type declaringType,
}

if (method instanceof Method) {
Type returnType = resolveType(declaringType, ((Method) method).getGenericReturnType());
Method asMethod = (Method) method;
Type returnType = resolveType(declaringType, asMethod.getGenericReturnType());

stringBuilder.append(typeToString(returnType, useSimpleName))
.append(' ');
stringBuilder.append(method.getName());
stringBuilder.append(asMethod.getName());
} else {
final Class<?> declaringClass = method.getDeclaringClass();
final Class<?> declaringClass = ((Constructor<?>) method).getDeclaringClass();
stringBuilder.append(typeToString(declaringClass, useSimpleName));
}

stringBuilder.append('(');
Type[] unresolvedParameterTypes = method.getGenericParameterTypes();

for (int i = 0; i < unresolvedParameterTypes.length; ++i) {
final Type parameterType = resolveType(declaringType, unresolvedParameterTypes[i]);
String parameterName = typeToString(parameterType, useSimpleName);

if (method.isVarArgs() && i == unresolvedParameterTypes.length - 1) {
if (varArgs && i == unresolvedParameterTypes.length - 1) {
parameterName = parameterName.replaceFirst("\\[\\]$", "...");
}

Expand Down Expand Up @@ -581,7 +602,7 @@ public static String typeToString(Type type, boolean useSimpleName) {
return clazz.getSimpleName();
}

return clazz.getTypeName();
return classTypeName(clazz);
}

if (type instanceof WildcardType) {
Expand All @@ -599,6 +620,30 @@ public static String typeToString(Type type, boolean useSimpleName) {
return null;
}

/**
* Equivalent to {@link Class#getTypeName()}, which isn't available before Android API 26.
* Unlike {@link Class#getName()}, array types are rendered as e.g. {@code java.lang.String[]}
* rather than the JVM-internal {@code [Ljava.lang.String;}.
*/
private static String classTypeName(Class<?> clazz) {
if (!clazz.isArray()) {
return clazz.getName();
}

Class<?> componentType = clazz;
int dimensions = 0;
while (componentType.isArray()) {
dimensions++;
componentType = componentType.getComponentType();
}

StringBuilder stringBuilder = new StringBuilder(componentType.getName());
for (int i = 0; i < dimensions; i++) {
stringBuilder.append("[]");
}
return stringBuilder.toString();
}


/**
* Tries to resolve a java type name to a Class
Expand Down
54 changes: 42 additions & 12 deletions nui/src/main/java/org/terasology/nui/widgets/UIText.java
Original file line number Diff line number Diff line change
Expand Up @@ -600,29 +600,59 @@ protected void paste() {
/**
* Get the current clipboard contents.
*
* @return The string currently in the clipboard
* @return The string currently in the clipboard, or an empty string if the system clipboard
* isn't available on this platform (e.g. Android, which has no java.awt).
*/
protected String getClipboardContents() {
Transferable t = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null);

try {
if (t != null && t.isDataFlavorSupported(DataFlavor.stringFlavor)) {
return (String) t.getTransferData(DataFlavor.stringFlavor);
}
} catch (UnsupportedFlavorException | IOException e) {
logger.warn("Failed to get data from clipboard", e);
return AwtClipboard.getContents();
} catch (LinkageError e) {
logger.warn("System clipboard is not available on this platform", e);
return "";
}

return "";
}

/**
* Set the contents of the clipboard to a given value.
* Set the contents of the clipboard to a given value. Does nothing if the system clipboard
* isn't available on this platform (e.g. Android, which has no java.awt).
*
* @param str The new value of the clipboard contents
*/
protected void setClipboardContents(String str) {
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(str), null);
try {
AwtClipboard.setContents(str);
} catch (LinkageError e) {
logger.warn("System clipboard is not available on this platform", e);
}
}

/**
* Isolates the java.awt.datatransfer clipboard access in its own class, rather than referencing
* it directly in getClipboardContents()/setClipboardContents(), so that loading UIText itself
* doesn't require java.awt to be resolvable - it genuinely doesn't exist on Android, unlike the
* other API-level gaps elsewhere in this codebase that have a same-behavior workaround.
* AwtClipboard is only classloaded the first time one of those two methods actually runs, and
* the LinkageError (a NoClassDefFoundError, on a platform without java.awt) is caught by the
* caller rather than here, since the failure happens on this class's own initialization.
*/
private static final class AwtClipboard {
static String getContents() {
Transferable t = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null);

try {
if (t != null && t.isDataFlavorSupported(DataFlavor.stringFlavor)) {
return (String) t.getTransferData(DataFlavor.stringFlavor);
}
} catch (UnsupportedFlavorException | IOException e) {
logger.warn("Failed to get data from clipboard", e);
}

return "";
}

static void setContents(String str) {
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(str), null);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
Expand Down Expand Up @@ -145,11 +144,10 @@ private void populateConstructorParameters(Binding<T> binding,
Binding<Constructor<T>> selectedConstructor) {
parameterLayout.removeAllWidgets();

Parameter[] parameters = selectedConstructor.get().getParameters();

// java.lang.reflect.Parameter isn't available before Android API 26 - Constructor's older
// getGenericParameterTypes() gets us the same Type[] without going through it.
List<TypeInfo<?>> parameterTypes =
Arrays.stream(parameters)
.map(Parameter::getParameterizedType)
Arrays.stream(selectedConstructor.get().getGenericParameterTypes())
.map(parameterType -> ReflectionUtil.resolveType(type.getType(), parameterType))
.map(TypeInfo::of)
.collect(Collectors.toList());
Expand Down Expand Up @@ -186,14 +184,13 @@ private void populateConstructorParameters(Binding<T> binding,
for (int i = 0; i < parameterTypes.size(); i++) {
TypeInfo<?> parameterType = parameterTypes.get(i);
Binding<?> argumentBinding = argumentBindings.get(i);
Parameter parameter = parameters[i];

Optional<UIWidget> optionalWidget =
library.getBaseTypeWidget((Binding) argumentBinding, parameterType);

if (!optionalWidget.isPresent()) {
LOGGER.warn("Could not create widget for parameter of type {} of constructor {}",
parameter, selectedConstructor.get());
parameterType, selectedConstructor.get());
continue;
}

Expand Down