I have a GraphQL query similar to this:
query {
getPosts {
...PostFragment
}
}
fragment SpecificPostFragment on SpecificPost {
owner {
id
name
}
}
fragment PostFragment on Post {
id
object
... on SpecificPost {
...SpecificPostFragment
}
}
I try to know if:
the field object is requested
the field owner is requested
I try to apply what is written here:
https://www.graphql-java.com/documentation/v11/fieldselection/
But dataFetchingEnvironment.getSelectionSet().contains("XXX") does not seem to work well with fragments.
How to do that ?
I haven't found any built-in solution, so I wrote my own. Here is my code
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import graphql.execution.MergedField;
import graphql.language.Field;
import graphql.language.FragmentDefinition;
import graphql.language.FragmentSpread;
import graphql.language.InlineFragment;
import graphql.language.SelectionSet;
import graphql.schema.DataFetchingEnvironment;
public class GraphQLUtil {
private static class PathElement {
private final String name;
private final String typeName;
public PathElement(String name, String typeName) {
this.name = name;
this.typeName = typeName;
}
public String getName() {
return name;
}
public String getTypeName() {
return typeName;
}
}
public static boolean containsIncludingFragments(DataFetchingEnvironment env, String path) {
Objects.requireNonNull(env, "The data fetching environment must not be null");
Objects.requireNonNull(path, "The field path must not be null");
List<PathElement> elts = Stream.of(path.split("/")).map(p -> {
String pt = p.trim();
if (pt.isEmpty()) {
throw new IllegalArgumentException("Empty path element found");
}
int sepIdx = pt.indexOf(":");
String name = pt;
String typeName = null;
if (sepIdx >= 0) {
typeName = pt.substring(0, sepIdx);
name = pt.substring(sepIdx + 1, pt.length());
}
return new PathElement(name, typeName);
}).collect(Collectors.toList());
if (elts.isEmpty()) {
return false;
}
MergedField mf = env.getMergedField();
return searchPathElement(env, elts, 0, mf.getSingleField().getSelectionSet(), null);
}
private static boolean searchPathElement(
DataFetchingEnvironment env,
List<PathElement> elts,
int eltIndex,
SelectionSet selectionSet,
String selectionTypeName) {
if (eltIndex >= elts.size()) {
return true;
}
PathElement currentElt = elts.get(eltIndex);
String currentName = currentElt.getName();
String currentTypeName = currentElt.getTypeName();
List<Field> fields = selectionSet.getSelectionsOfType(Field.class);
boolean found = false;
for (Field f : fields) {
if (f.getName().equals(currentName) && (currentTypeName == null
|| selectionTypeName == null
|| currentTypeName.equals(selectionTypeName))) {
found = searchPathElement(env, elts, eltIndex + 1, f.getSelectionSet(), null);
if (found) {
return true;
}
}
}
List<FragmentSpread> fragments = selectionSet.getSelectionsOfType(FragmentSpread.class);
for (FragmentSpread f : fragments) {
FragmentDefinition fDef = env.getFragmentsByName().get(f.getName());
found = searchPathElement(env, elts, eltIndex, fDef.getSelectionSet(), fDef.getTypeCondition().getName());
if (found) {
return true;
}
}
List<InlineFragment> inlineFragments = selectionSet.getSelectionsOfType(InlineFragment.class);
for (InlineFragment f : inlineFragments) {
found = searchPathElement(env, elts, eltIndex, f.getSelectionSet(), f.getTypeCondition().getName());
if (found) {
return true;
}
}
return false;
}
}
And you call it like this:
DataFetchingEnvironment dataEnv = ... // If like me you use GraphQL SPQR, you can get it with io.leangen.graphql.execution.ResolutionEnvironment
boolean t1= GraphQLUtil.containsIncludingFragments(dataEnv, "id");
boolean t2 = GraphQLUtil.containsIncludingFragments(dataEnv, "owner/id");
boolean t3 = GraphQLUtil.containsIncludingFragments(dataEnv, "SpecificPost:owner/id"); // You may give the type of the field, if in some inheritance scenario, it is ambiguous
Note that this solution does not support wild card (* or ?). And I haven't tested it if the main query contains multiple entries (getPost + getPeople in the same query for example), but that probably does not work in that case.
Related
i'm using "openapi-generator-maven-plugin" version 6.2.1 for generate modules from a YAML file and consume a RestAPI.
The code that is generated from the plugin looks fine and it work great with "GET" requests, the problem that i've encountered so far, is on "POST" requests. Apparently when I pass an object with null values (even if they are nullable) it throws "Unable to serialize property" from [org.eclipse.yasson.internal.Marshaller], even if the object is Serializable.
More specific:
I've this object generated from the plugin.
/*
* MS00140
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
package org.openapitools.client.model;
import java.util.Objects;
import java.util.Arrays;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.annotation.JsonValue;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.openapitools.jackson.nullable.JsonNullable;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.openapitools.jackson.nullable.JsonNullable;
import java.util.NoSuchElementException;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.annotation.JsonTypeName;
/**
* ModelFile
*/
#JsonPropertyOrder({
ModelFile.JSON_PROPERTY_LINK,
ModelFile.JSON_PROPERTY_NAME,
ModelFile.JSON_PROPERTY_MIME_TYPE,
ModelFile.JSON_PROPERTY_DATA
})
#JsonTypeName("File")
#javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", date = "2022-11-21T13:50:57.834020300+01:00[Europe/Berlin]")
public class ModelFile implements Serializable {
private static final long serialVersionUID = 1L;
public static final String JSON_PROPERTY_LINK = "link";
private JsonNullable<String> link = JsonNullable.<String>undefined();
public static final String JSON_PROPERTY_NAME = "name";
private JsonNullable<String> name = JsonNullable.<String>undefined();
public static final String JSON_PROPERTY_MIME_TYPE = "mimeType";
private JsonNullable<String> mimeType = JsonNullable.<String>undefined();
public static final String JSON_PROPERTY_DATA = "data";
private JsonNullable<byte[]> data = JsonNullable.<byte[]>undefined();
public ModelFile() {
}
public ModelFile link(String link) {
this.link = JsonNullable.<String>of(link);
return this;
}
/**
* Get link
* #return link
**/
#javax.annotation.Nullable
#ApiModelProperty(value = "")
#JsonIgnore
public String getLink() {
return link.orElse(null);
}
#JsonProperty(JSON_PROPERTY_LINK)
#JsonInclude(value = JsonInclude.Include.USE_DEFAULTS)
public JsonNullable<String> getLink_JsonNullable() {
return link;
}
#JsonProperty(JSON_PROPERTY_LINK)
public void setLink_JsonNullable(JsonNullable<String> link) {
this.link = link;
}
public void setLink(String link) {
this.link = JsonNullable.<String>of(link);
}
public ModelFile name(String name) {
this.name = JsonNullable.<String>of(name);
return this;
}
/**
* Get name
* #return name
**/
#javax.annotation.Nullable
#ApiModelProperty(value = "")
#JsonIgnore
public String getName() {
return name.orElse(null);
}
#JsonProperty(JSON_PROPERTY_NAME)
#JsonInclude(value = JsonInclude.Include.USE_DEFAULTS)
public JsonNullable<String> getName_JsonNullable() {
return name;
}
#JsonProperty(JSON_PROPERTY_NAME)
public void setName_JsonNullable(JsonNullable<String> name) {
this.name = name;
}
public void setName(String name) {
this.name = JsonNullable.<String>of(name);
}
public ModelFile mimeType(String mimeType) {
this.mimeType = JsonNullable.<String>of(mimeType);
return this;
}
/**
* Get mimeType
* #return mimeType
**/
#javax.annotation.Nullable
#ApiModelProperty(value = "")
#JsonIgnore
public String getMimeType() {
return mimeType.orElse(null);
}
#JsonProperty(JSON_PROPERTY_MIME_TYPE)
#JsonInclude(value = JsonInclude.Include.USE_DEFAULTS)
public JsonNullable<String> getMimeType_JsonNullable() {
return mimeType;
}
#JsonProperty(JSON_PROPERTY_MIME_TYPE)
public void setMimeType_JsonNullable(JsonNullable<String> mimeType) {
this.mimeType = mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = JsonNullable.<String>of(mimeType);
}
public ModelFile data(byte[] data) {
this.data = JsonNullable.<byte[]>of(data);
return this;
}
/**
* Get data
* #return data
**/
#javax.annotation.Nullable
#ApiModelProperty(value = "")
#JsonIgnore
public byte[] getData() {
return data.orElse(null);
}
#JsonProperty(JSON_PROPERTY_DATA)
#JsonInclude(value = JsonInclude.Include.USE_DEFAULTS)
public JsonNullable<byte[]> getData_JsonNullable() {
return data;
}
#JsonProperty(JSON_PROPERTY_DATA)
public void setData_JsonNullable(JsonNullable<byte[]> data) {
this.data = data;
}
public void setData(byte[] data) {
this.data = JsonNullable.<byte[]>of(data);
}
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ModelFile _file = (ModelFile) o;
return equalsNullable(this.link, _file.link) &&
equalsNullable(this.name, _file.name) &&
equalsNullable(this.mimeType, _file.mimeType) &&
equalsNullable(this.data, _file.data);
}
private static <T> boolean equalsNullable(JsonNullable<T> a, JsonNullable<T> b) {
return a == b || (a != null && b != null && a.isPresent() && b.isPresent() && Objects.deepEquals(a.get(), b.get()));
}
#Override
public int hashCode() {
return Objects.hash(hashCodeNullable(link), hashCodeNullable(name), hashCodeNullable(mimeType), hashCodeNullable(data));
}
private static <T> int hashCodeNullable(JsonNullable<T> a) {
if (a == null) {
return 1;
}
return a.isPresent() ? Arrays.deepHashCode(new Object[]{a.get()}) : 31;
}
#Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class ModelFile {\n");
sb.append(" link: ").append(toIndentedString(link)).append("\n");
sb.append(" name: ").append(toIndentedString(name)).append("\n");
sb.append(" mimeType: ").append(toIndentedString(mimeType)).append("\n");
sb.append(" data: ").append(toIndentedString(data)).append("\n");
sb.append("}");
return sb.toString();
}
/**
* Convert the given object to string with each line indented by 4 spaces
* (except the first line).
*/
private String toIndentedString(Object o) {
if (o == null) {
return "null";
}
return o.toString().replace("\n", "\n ");
}
}
When I make the request the only property that I set for the moment is the "link" property, because the REST service knows what to do.
public File getFileByLink(String link) throws WebServiceException, DataNotFoundException {
log.debug("Executing---> getFileByLink()");
File file;
try {
ModelFile inputFile = new ModelFile();
inputFile.setLink(link);
file = utilityApi.apiUtilityGetFileByLinkPost(inputFile);
if (file == null) {
throw new DataNotFoundException("Error!");
}
} catch (ApiException e) {
throw new WebServiceException(e.getCode() + " - " + e.getMessage());
} catch (DataNotFoundException e) {
throw e;
} catch (Exception e) {
throw new WebServiceException(e);
}
log.debug("Leaving ---> getFileByLink()");
return file;
}
Now when it perform the actual request and try to serialize the object throws this error:
13:55:36,124 SEVERE [org.eclipse.yasson.internal.Marshaller] (default task-1) Unable to serialize property 'data_JsonNullable' from org.openapitools.client.model.ModelFile
13:55:36,133 SEVERE [org.eclipse.yasson.internal.Marshaller] (default task-1) Generating incomplete JSON
13:55:36,145 ERROR [it.inps.itg.web.exception.WebServiceException] (default task-1) WebServiceException in Class: TemplateAction.java and Method: executeAction Message: RESTEASY004655: Unable to invoke request: javax.ws.rs.ProcessingException: RESTEASY008205: JSON Binding serialization error javax.json.bind.JsonbException: Unable to serialize property 'data_JsonNullable' from org.openapitools.client.model.ModelFile Cause: javax.ws.rs.ProcessingException: RESTEASY008205: JSON Binding serialization error javax.json.bind.JsonbException: Unable to serialize property 'data_JsonNullable' from org.openapitools.client.model.ModelFile
At first I didn't use the property <serializableModel>true</serializableModel>, and I thought that the object could not be searialized for this problem, but even if the class implement the Serializable interface the problem still occur.
If can help, I've tried to set values to all the object's properties and as long as they are not null, there's non serialization problems. The problem occur when there's at least one property that is null.
I'm trying to make a program that compares object archive to an Arraylist archives and only add it to the arraylist if it isn't in it, but am really struggling. I've tried modifying the for loop, to adding a new boolean to check if there is there is already an identical archive but just can't seem to get it to only add unique archives. Any advice would be greatly appreciated.
import java.util.ArrayList;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
ArrayList<Archive> archives = new ArrayList<>();
while (true) {
System.out.println("Identifier? (empty will stop)");
String identifier = scanner.nextLine();
if (identifier.isEmpty()) {
break;
}
System.out.println("Name? (empty will stop)");
String name = scanner.nextLine();
if (name.isEmpty()) {
break;
}
Archive archive = new Archive(identifier, name);
boolean contains = false;
for (int i = 0; i < archives.size(); i++) {
if (archive.equals(archives.get(i))) {
contains = true;
break;
}
}
if (contains == false) {
archives.add(archive);
}
}
for (Archive i : archives) {
System.out.println(i.getIdentifier() + ": " + i.getName());
}
}
}
public class Archive {
private String identifier;
private String name;
public Archive(String identifier, String name) {
this.identifier = identifier;
this.name = name;
}
public String getIdentifier() {
return this.identifier;
}
public String getName() {
return this.name;
}
public boolean equals(Archive archive, Object compared) {
if (compared == archive) {
return true;
}
if (!(compared instanceof Archive)) {
return false;
}
Archive compare = (Archive) compared;
if (compare.getIdentifier().equals(archive.getIdentifier()) && compare.getName().equals(archive.getName())) {
return true;
}
return false;
}
}
//Also just realised that instead of the whole for loop with boolean contains thing I can just do
if(archives.contains(archive){
contains=true;}
The signature of equals method is wrong.
It should be
public boolean equals(Object compared) {
if(compared==null || !(compared instanceof Archive)){
return false;
}
Archive compare = (Archive) compared;
if (compare.getIdentifier().equals(this.getIdentifier()) && compare.getName().equals(this.getName())) {
return true;
}
return false;
}
We had a discussion at work about pros and cons of static (from a built JAR) vs dynamic (from separate location in CLASS_PATH) loading of Java libraries.
In the middle of that discussion, it occurred to me: regardless of which side is right, maybe there's a way to have your cake and eat it too:
Have a default custom class loader for your organization
Class loader - when loading a specific library - checks a configuration that, for every library (or app+library combo for more granularity), contains a flag that determines if the library is to be loaded statically from the JAR or dynamically from CLASS_PATH
The apps all get built with the library classes in their JARs (default "backup" version of the library to use if you don't want to dynamically load new library version)
If you wish your library to be loaded statically (e.g. because new version is incompatible with older one, or just to eliminate change risk if warranted), you set the configuration flag for that library to true.
If instead your change risk is zero/low, you set the flag to false; and allow the library to be loaded dynamically, thus allowing you to release new versions of the library that get picked up by ALL apps without re-compilation and re-release (obviously, the apps would need to all be tested with new library for this to be anything but a minefield of bugs approach).
Whether my idea is good or bad, what I would like to know is, whether my second bullet point is even technically feasible with Java (say 1.8+), and if so, what would be involved in implementing it?
Yes, absolutely. I will assume Java 8 for this answer , as I have yet to familiarize myself with the capabilities of Java 9's Module System.
The main idea is straightforward--a ClassLoader whose loadClass(String, boolean) implementation adheres to the following "protocol":
If the class name argument refers to a system class, delegate loading to your parent loader and return. Otherwise proceed.
Does the name, according to your configuration, refer to an application class? If so proceed to step 2.1, otherwise to 3.
Get the application "search path" (the filesystem path of the JAR file itself according to the requirements in the question).
Attempt to find, in an implementation-specific manner (by means of e.g. path resolution against the search path's entries), a class resource below the search path that matches the name argument. If such a resource exists, proceed to step 5, otherwise to 3.
For each known library not yet examined, sorted by descending priority:
Does the name refer to a class of that library? If so continue; otherwise return to 3.
Get the search path for the particular library.
Attempt to find a class resource under the search path that matches the name argument. If successful, proceed to step 5, otherwise return to step 3.
If no matching class resource was established in step 2 or 3, raise an exception. Otherwise continue.
Retrieve the content of the resource, delegate to defineClass and resolveClass as appropriate, and return the new class.
Below is a (pretty ugly) sample implementation of such a ClassLoader.
Assumptions:
A module is an application or a library.
A module comprises a known set of classes; in other words, a module "is aware of its contents".
An application uses zero or more libraries.
Multiple logical applications may be running on the same JVM.
A Configuration communicates to its ClassLoader:
The "current" application requesting class loading.
Mappings between applications and search paths.
Mappings between library-application pairs and search paths.
Multiple loaders may use (the persisted representation of) the same configuration.
package com.example.q45313762;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import java.util.function.Supplier;
public final class ConfigurableClasspathClassLoader extends URLClassLoader {
public interface Configuration {
interface Module {
String getName();
String getVersion();
boolean includes(String resourceName);
}
interface Library extends Module {}
interface Application extends Module {}
enum LoadingMode {
STATIC, DYNAMIC;
}
Application getCurrentApplication();
Iterable<URL> getLibrarySearchPath(Library lib, LoadingMode mode, Application app);
Iterable<URL> getApplicationSearchPath(Application app);
Iterable<Library> getApplicationLibraries(Application app);
}
public static final class SimpleStaticConfiguration implements Configuration {
private static abstract class SimpleModule implements Module {
private final String name, version;
private final Predicate<String> resourceNameMatcher;
private SimpleModule(String name, String version, Predicate<String> resourceNameMatcher) {
requireNoneNull(name, version, resourceNameMatcher);
name = name.trim();
version = version.trim();
if (name.isEmpty() || version.isEmpty()) {
throw new IllegalArgumentException("arguments must not be empty.");
}
this.name = name;
this.version = version;
this.resourceNameMatcher = resourceNameMatcher;
}
#Override
public String getName() {
return name;
}
#Override
public String getVersion() {
return version;
}
#Override
public boolean includes(String resourceName) {
if (resourceName == null) {
return false;
}
return resourceNameMatcher.test(resourceName);
}
#Override
public final int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((resourceNameMatcher == null) ? 0 : resourceNameMatcher.hashCode());
result = prime * result + ((version == null) ? 0 : version.hashCode());
return result;
}
#Override
public final boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof SimpleModule)) {
return false;
}
SimpleModule other = (SimpleModule) obj;
if (name == null) {
if (other.name != null) {
return false;
}
}
else if (!name.equals(other.name)) {
return false;
}
if (resourceNameMatcher == null) {
if (other.resourceNameMatcher != null) {
return false;
}
}
else if (!resourceNameMatcher.equals(other.resourceNameMatcher)) {
return false;
}
if (version == null) {
if (other.version != null) {
return false;
}
}
else if (!version.equals(other.version)) {
return false;
}
return true;
}
}
public static final class SimpleLibrary extends SimpleModule implements Library {
public SimpleLibrary(String name, String version, Predicate<String> resourceNameMatcher) {
super(name, version, resourceNameMatcher);
}
}
public static final class SimpleApplication extends SimpleModule implements Application {
public SimpleApplication(String name, String version, Predicate<String> resourceNameMatcher) {
super(name, version, resourceNameMatcher);
}
}
private static final class ModuleRegistry {
private static abstract class Key {
private final Module module;
private Key(Module module) {
requireNoneNull(module);
requireNoneNull(module.getName(), module.getVersion());
this.module = module;
}
private Module getModule() {
return module;
}
}
private static final class LibraryKey extends Key {
private final LoadingMode mode;
private final Application app;
private LibraryKey(Library lib, LoadingMode mode, Application app) {
super(lib);
requireNoneNull(mode);
requireNoneNull(app);
this.mode = mode;
this.app = app;
}
private Library getLibrary() {
return (Library) super.getModule();
}
private LoadingMode getLoadingMode() {
return mode;
}
private Application getApplication() {
return app;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
Library lib = getLibrary();
result = prime * result + ((lib == null) ? 0 : lib.hashCode());
result = prime * result + ((mode == null) ? 0 : mode.hashCode());
result = prime * result + ((app == null) ? 0 : app.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof LibraryKey)) {
return false;
}
LibraryKey other = (LibraryKey) obj;
Library thisLib = getLibrary(), othersLib = other.getLibrary();
if (thisLib == null) {
if (othersLib != null) {
return false;
}
}
else if (!thisLib.equals(othersLib)) {
return false;
}
if (mode != other.mode) {
return false;
}
if (app == null) {
if (other.app != null) {
return false;
}
}
else if (!app.equals(other.app)) {
return false;
}
return true;
}
}
private static final class ApplicationKey extends Key {
private ApplicationKey(Application app) {
super(app);
}
private Application getApplication() {
return (Application) super.getModule();
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
Application app = getApplication();
result = prime * result + ((app == null) ? 0 : app.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof ApplicationKey)) {
return false;
}
ApplicationKey other = (ApplicationKey) obj;
Application thisApp = getApplication(), othersApp = other.getApplication();
if (thisApp == null) {
if (othersApp != null) {
return false;
}
}
else if (!thisApp.equals(othersApp)) {
return false;
}
return true;
}
}
private static final class Value {
private final Set<URL> searchPath;
private Value(URL... searchPath) {
requireNoneNull((Object) searchPath);
if (searchPath == null || searchPath.length == 0) {
this.searchPath = EMPTY_SEARCH_PATH;
}
else {
this.searchPath = new LinkedHashSet<>(Arrays.asList(searchPath));
Iterator<URL> itr = this.searchPath.iterator();
while (itr.hasNext()) {
URL searchPathEntry = itr.next();
String proto = searchPathEntry.getProtocol();
if ("file".equals(proto) || "jar".equals(proto)) {
continue;
}
itr.remove();
}
verify();
}
}
private Set<URL> getSearchPath() {
verify();
return (searchPath == EMPTY_SEARCH_PATH) ? searchPath : Collections.unmodifiableSet(searchPath);
}
private void verify() {
Iterator<URL> itr = searchPath.iterator();
while (itr.hasNext()) {
try {
if (!Files.exists(Paths.get(itr.next().toURI()))) {
itr.remove();
}
}
catch (IllegalArgumentException | URISyntaxException | SecurityException e) {
itr.remove();
}
}
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((searchPath == null) ? 0 : searchPath.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof Value)) {
return false;
}
Value other = (Value) obj;
if (searchPath == null) {
if (other.searchPath != null) {
return false;
}
}
else if (!searchPath.equals(other.searchPath)) {
return false;
}
return true;
}
}
private final Map<Key, Value> m = new LinkedHashMap<>();
private Supplier<Application> appProvider;
private ModuleRegistry() {
}
private ModuleRegistry(ModuleRegistry mr) {
m.putAll(mr.m);
appProvider = mr.appProvider;
}
private void putLibraryEntry(Library lib, LoadingMode mode, Application app, URL... searchPath) {
m.put(new LibraryKey(lib, mode, app), new Value(searchPath));
}
private void putApplicationEntry(Application app, URL... searchPath) {
m.put(new ApplicationKey(app), new Value(searchPath));
}
private Set<Library> getLibraries(Application app) {
Set<Library> ret = null;
for (Key k : m.keySet()) {
if (!(k instanceof LibraryKey)) {
continue;
}
LibraryKey lk = (LibraryKey) k;
if (lk.getApplication().equals(app)) {
if (ret == null) {
ret = new LinkedHashSet<>();
}
ret.add(lk.getLibrary());
}
}
if (ret == null) {
ret = NO_LIBS;
}
return ret;
}
private Set<URL> getLibrarySearchPath(Library lib, LoadingMode mode, Application app) {
Set<URL> ret = EMPTY_SEARCH_PATH;
Value v = m.get(new LibraryKey(lib, mode, app));
if (mode == LoadingMode.DYNAMIC && (v == null || v.getSearchPath().isEmpty())) {
v = m.get(new LibraryKey(lib, LoadingMode.STATIC, app));
}
if (v != null) {
ret = v.getSearchPath();
}
return ret;
}
private Set<URL> getApplicationSearchPath(Application app) {
Set<URL> ret = EMPTY_SEARCH_PATH;
Value v = m.get(new ApplicationKey(app));
if (v != null) {
ret = v.getSearchPath();
}
return ret;
}
private Supplier<Application> getApplicationProvider() {
return appProvider;
}
private void setApplicationProvider(Supplier<Application> appProvider) {
requireNoneNull(appProvider);
requireNoneNull(appProvider.get());
this.appProvider = appProvider;
}
private void clear() {
m.clear();
}
}
public static final class Builder {
private final ModuleRegistry registry = new ModuleRegistry();
private Builder() {
}
public synchronized Builder withLibrary(Library lib, LoadingMode mode, Application app, URL... searchPath) {
registry.putLibraryEntry(lib, mode, app, searchPath);
return this;
}
public synchronized Builder withApplication(Application app, URL... searchPath) {
registry.putApplicationEntry(app, searchPath);
return this;
}
public synchronized Builder withApplicationProvider(Supplier<Application> appProvider) {
registry.setApplicationProvider(appProvider);
return this;
}
public synchronized SimpleStaticConfiguration build() {
SimpleStaticConfiguration ret = new SimpleStaticConfiguration(this);
registry.clear();
return ret;
}
public synchronized Builder reset() {
registry.clear();
return this;
}
}
public static final Set<URL> EMPTY_SEARCH_PATH = Collections.emptySet();
private static final Set<Library> NO_LIBS = Collections.emptySet();
public static Builder newBuilder() {
return new Builder();
}
private final ModuleRegistry registry;
private SimpleStaticConfiguration(Builder b) {
registry = new ModuleRegistry(b.registry);
}
#Override
public Application getCurrentApplication() {
return registry.getApplicationProvider().get();
}
#Override
public Iterable<URL> getLibrarySearchPath(Library lib, LoadingMode mode, Application app) {
return registry.getLibrarySearchPath(lib, mode, app);
}
#Override
public Iterable<URL> getApplicationSearchPath(Application app) {
return registry.getApplicationSearchPath(app);
}
#Override
public Iterable<Library> getApplicationLibraries(Application app) {
return registry.getLibraries(app);
}
}
private static final String JAVA_HOME_PROP = System.getProperty("java.home");
private static void requireNoneNull(Object... args) {
if (args != null) {
for (Object o : args) {
Objects.requireNonNull(o);
}
}
}
private final Lock readLock, writeLock;
private Configuration cfg;
{
ReadWriteLock rwl = new ReentrantReadWriteLock(false);
readLock = rwl.readLock();
writeLock = rwl.writeLock();
}
public ConfigurableClasspathClassLoader(Configuration cfg, ClassLoader parent) {
super(new URL[0], parent);
setConfiguration(cfg);
}
public void setConfiguration(Configuration cfg) {
requireNoneNull(cfg);
try {
writeLock.lock();
this.cfg = cfg;
}
finally {
writeLock.unlock();
}
}
#Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (name == null) {
throw new ClassNotFoundException(name);
}
synchronized (getClassLoadingLock(name)) {
Class<?> ret;
Class<?> self = getClass();
if (self.getName().equals(name)) {
// no need to "reload" our own class
return self;
}
ret = findLoadedClass(name);
if (ret != null) {
// already loaded
return ret;
}
// unknown
ret = findClass(name);
if (resolve) {
resolveClass(ret);
}
return ret;
}
}
#Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// perform a search on the global classpath (obviously far from ideal)
Enumeration<URL> allMatches;
String modifiedName = name.replace(".", "/").concat(".class");
try {
allMatches = getResources(modifiedName);
}
catch (IOException ioe) {
throw new ClassNotFoundException(name);
}
Set<URL> filteredMatches = new LinkedHashSet<>();
while (allMatches.hasMoreElements()) {
URL match = allMatches.nextElement();
if (match.getPath().replaceFirst("file:", "").startsWith(JAVA_HOME_PROP)) {
// probably a bootstrap classpath class - these are off limits to us
return getParent().loadClass(name);
}
// candidate match
filteredMatches.add(match);
}
if (!filteredMatches.isEmpty()) {
try {
readLock.lock();
BiFunction<Configuration.Module, Iterable<URL>, URL[]> matcher = (module, searchPath) -> {
URL[] ret = null;
if (module.includes(name)) {
outer: for (URL searchPathEntry : searchPath) {
for (URL filteredMatch : filteredMatches) {
if (filteredMatch != null && filteredMatch.getPath().replaceFirst("file:", "")
.startsWith(searchPathEntry.getPath())) {
ret = new URL[] { filteredMatch, searchPathEntry };
break outer;
}
}
}
}
return ret;
};
Configuration.Application app = cfg.getCurrentApplication();
URL matchedClassResource = null, matchingSearchPath = null;
if (app != null) {
// try an application search path match
URL[] tmp = matcher.apply(app, cfg.getApplicationSearchPath(app));
if (tmp != null) {
matchedClassResource = tmp[0];
matchingSearchPath = tmp[1];
}
else {
// try matching against the search path of any library "known to" app
for (Configuration.Library lib : cfg.getApplicationLibraries(app)) {
tmp = matcher.apply(lib,
cfg.getLibrarySearchPath(lib, Configuration.LoadingMode.DYNAMIC, app));
if (tmp != null) {
matchedClassResource = tmp[0];
matchingSearchPath = tmp[1];
break;
}
}
}
if (matchedClassResource != null) {
// matched - load
byte[] classData = readClassData(matchedClassResource);
return defineClass(name, classData, 0, classData.length,
constructClassDomain(matchingSearchPath));
}
}
}
finally {
readLock.unlock();
}
}
throw new ClassNotFoundException(name);
}
private byte[] readClassData(URL classResource) {
try (InputStream in = new BufferedInputStream(classResource.openStream());
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
while (in.available() > 0) {
out.write(in.read());
}
return out.toByteArray();
}
catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
private ProtectionDomain constructClassDomain(URL codeSourceLocation) {
CodeSource cs = new CodeSource(codeSourceLocation, (Certificate[]) null);
return new ProtectionDomain(cs, getPermissions(cs), this, null);
}
}
Notes:
Search paths registered with the loader must be subsets (subtrees) of the effective classpath ("java.class.path" property). Furthermore, "fat JARs" are not supported.
I could not include a usage example due to post length constraints. I will provide one via gist if requested.
Eclipse can auto-generate a toString() method from a object's fields. If those fields are objects then they too may have similarly auto-generated toString() methods.
e.g. a President object might look like this:
President [country=USA, name=Name [title=Mr, forename=Barack, surname=Obama], address=Address [houseNumber=1600, street=Pennsylvania Avenue, town=Washington]]
which is easier to read if I format it:
President [
country=USA,
name=Name [
title=Mr,
forename=Barack,
surname=Obama],
address=Address [
houseNumber=1600,
street=Pennsylvania Avenue,
town=Washington]]
What is the best way to parse this String to create a map of maps?
I've got a solution, but it's not pretty. I was hoping to be able to avoid the low level String manipulation somehow, but here it is:
import java.util.LinkedHashMap;
import java.util.Map;
public class MappedObject {
public String className;
public Map<String, String> leafFields = new LinkedHashMap<>();
public Map<String, MappedObject> treeFields = new LinkedHashMap<>();
#Override
public String toString() {
return "[className=" + className
+ (leafFields.isEmpty() ? "" : ", leafFields=" + leafFields)
+ (treeFields.isEmpty() ? "" : ", treeFields=" + treeFields)
+ "]";
}
public static MappedObject createFromString(String s) {
MappedObject mo = new MappedObject();
new Mapper(s).mapObject(mo);
return mo;
}
private static class Mapper {
private String s;
public Mapper(String s) {
this.s = s;
}
private String mapObject(MappedObject mo) {
mo.className = removeFirstNCharacters(s.indexOf(' '));
while (s.contains("=")) {
removeLeadingNonLetters();
String key = removeFirstNCharacters(s.indexOf('='));
removeFirstNCharacters(1); // remove the =
String leafValue = getLeafValue();
if (leafValue != null) {
mo.leafFields.put(key, leafValue);
if (s.startsWith("]")) { // that was the last field in the tree
return s;
}
} else {
MappedObject treeField = new MappedObject();
mo.treeFields.put(key, treeField);
s = new Mapper(s).mapObject(treeField);
}
}
return s; // s contains only close brackets - ]
}
private void removeLeadingNonLetters() {
int i = 0;
while (!Character.isLetter(s.charAt(i))) {
i++;
}
removeFirstNCharacters(i);
}
private String removeFirstNCharacters(int n) {
String value = s.substring(0, n);
s = s.substring(value.length());
return value;
}
private String getLeafValue() {
int endIndex = getEndIndex();
if (!s.contains("[") || s.indexOf('[') > endIndex) {
return removeFirstNCharacters(endIndex);
}
return null;
}
/** The end of the value, if it's a leaf field. */
private int getEndIndex() {
if(s.contains(",")) {
return Math.min(s.indexOf(','), s.indexOf(']'));
}
return s.indexOf(']');
}
}
}
I have been trying to translate this tutorial into Java code, as I want to make a simple game with level/achievements in android (and haven't found as thorough/basic examples in java online, if you have one please share)
Please help me understand:
How can I link different file of classes together? in the example they don't seem to refer to each other? Basically how can I pass on the properties and settings from the tasks/games to these functions which are elsewhere in the code? do I just refer to the class several times throughout the code?
For example I am stuck in this part, could use help in understanding how this works in java code? (examples are most appreciated)
> private var mProps :Object; // dictionary of properties
private var mAchievements :Object; // dictionary of achievements
public function Achieve() {
mProps = { };
mAchievements = { };
}
public function defineProperty(theName :String, theInitialValue :int, theaActivationMode :String, theValue :int) :void {
mProps[theName] = new Property(theName, theInitialValue, theaActivationMode, theValue);
}
public function defineAchievement(theName :String, theRelatedProps :Array) :void {
mAchievements[theName] = new Achievement(theName, theRelatedProps);
}
Remember that each class must go to its own file.
public class Property {
private String mName;
private int mValue;
private String mActivation;
private int mActivationValue;
private int mInitialValue;
public Property(String theName, int theInitialValue, String theActivation, int theActivationValue) {
mName = theName;
mActivation = theActivation;
mActivationValue = theActivationValue;
mInitialValue = theInitialValue;
}
public int getValue() {
return mValue;
}
public void setValue(int n) {
mValue = n;
}
public boolean isActive() {
boolean aRet = false;
switch(mActivation) {
case Achieve.ACTIVE_IF_GREATER_THAN: aRet = mValue > mActivationValue; break;
case Achieve.ACTIVE_IF_LESS_THAN: aRet = mValue < mActivationValue; break;
case Achieve.ACTIVE_IF_EQUALS_TO: aRet = mValue == mActivationValue; break;
}
return aRet;
}
public String getActivation() {
return mActivation;
}
}
import java.util.ArrayList;
public class Achievement {
private String mName; // achievement name
private ArrayList<String> mProps; // array of related properties
private boolean mUnlocked; // achievement is unlocked or not
public Achievement(String theId, ArrayList<String> theRelatedProps) {
mName = theId;
mProps = theRelatedProps;
mUnlocked = false;
}
public boolean isUnlocked() {
return mUnlocked;
}
public void setUnlocked(boolean b) {
mUnlocked = b;
}
public ArrayList<String> getProps() {
return mProps;
}
}
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class Achieve {
// activation rules
public static final String ACTIVE_IF_GREATER_THAN = ">";
public static final String ACTIVE_IF_LESS_THAN = "<";
public static final String ACTIVE_IF_EQUALS_TO = "==";
private HashMap<String,Property> mProps; // dictionary of properties
private HashMap<String,Achievement> mAchievements; // dictionary of achievements
public Achieve() {
mProps = new HashMap<String,Property>();
mAchievements = new HashMap<String,Achievement>();
}
public void defineProperty(String theName, int theInitialValue, String theaActivationMode, int theValue) {
mProps.put(theName, new Property(theName, theInitialValue, theaActivationMode, theValue));
}
public void defineAchievement(String theName, ArrayList<String> theRelatedProps) {
mAchievements.put(theName, new Achievement(theName, theRelatedProps));
}
public int getValue(String theProp) {
Property p = mProps.get(theProp);
if (p != null) return p.getValue();
return 0;
}
public void setValue(String theProp, int theValue) {
Property p = mProps.get(theProp);
if (p == null) return;
switch(p.getActivation()) {
case Achieve.ACTIVE_IF_GREATER_THAN:
theValue = theValue > p.getValue() ? theValue : p.getValue();
break;
case Achieve.ACTIVE_IF_LESS_THAN:
theValue = theValue < p.getValue() ? theValue : p.getValue();
break;
}
p.setValue(theValue);
}
public void addValue(ArrayList<String> theProps, int theValue) {
for (int i = 0; i < theProps.size(); i++) {
String aPropName = theProps.get(i);
setValue(aPropName, getValue(aPropName) + theValue);
}
}
public ArrayList<Achievement> checkAchievements() {
ArrayList<Achievement> aRet = new ArrayList<Achievement>();
Iterator<Map.Entry<String,Achievement>> it = mAchievements.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String,Achievement> pair = it.next();
Achievement aAchievement = pair.getValue();
if (!aAchievement.isUnlocked()) {
int aActiveProps = 0;
ArrayList<String> props = aAchievement.getProps();
for (int p = 0; p < props.size(); p++) {
Property aProp= mProps.get(props.get(p));
if (aProp.isActive()) {
aActiveProps++;
}
}
if (aActiveProps == props.size()) {
aAchievement.setUnlocked(true);
aRet.add(aAchievement);
}
}
}
return aRet;
}
}