Remove json-simple.jar

This commit is contained in:
Reinhard Pointner 2016-03-07 19:46:47 +00:00
parent 9605ab7e63
commit a4d363bb97
8 changed files with 144 additions and 231 deletions

View File

@ -4,7 +4,6 @@
<classpathentry kind="src" path="test"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="lib" path="lib/ivy/bundle/args4j.jar"/>
<classpathentry kind="lib" path="lib/ivy/bundle/json-simple.jar"/>
<classpathentry kind="lib" path="lib/jars/jacksum.jar"/>
<classpathentry kind="lib" path="lib/jars/simmetrics.jar"/>
<classpathentry kind="lib" path="lib/jars/xmlrpc.jar"/>

View File

@ -82,10 +82,6 @@
<zipfileset src="${dir.dist}/filebot.jar" />
<!-- include libs -->
<zipfileset src="${dir.lib}/ivy/bundle/json-simple.jar">
<include name="org/json/simple/**" />
</zipfileset>
<zipfileset src="${dir.lib}/ivy/bundle/json-io.jar">
<include name="com/cedarsoftware/util/**" />
</zipfileset>

View File

@ -12,7 +12,6 @@
<dependency org="org.jsoup" name="jsoup" rev="1.+" />
<dependency org="org.tukaani" name="xz" rev="1.+" />
<dependency org="com.cedarsoftware" name="json-io" rev="4.3.+" />
<dependency org="com.googlecode.json-simple" name="json-simple" rev="1.+" />
<dependency org="commons-io" name="commons-io" rev="2.+" />
<dependency org="commons-net" name="commons-net" rev="3.+" />
<dependency org="org.slf4j" name="slf4j-jdk14" rev="1.+" />

View File

@ -4,6 +4,7 @@ import static java.util.Arrays.*;
import static java.util.Collections.*;
import static net.filebot.Logging.*;
import java.util.EnumMap;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Stream;
@ -72,20 +73,20 @@ public class JsonUtilities {
}
public static String getString(Object node, String key) {
Object value = asMap(node).get(key);
if (value != null) {
return value.toString();
}
return null;
return nonEmptyOrNull(asMap(node).get(key));
}
public static Integer getInteger(Object node, String key) {
return getStringValue(node, key, Integer::parseInt);
}
public static Double getDecimal(Object node, String key) {
return getStringValue(node, key, Double::parseDouble);
}
public static <V> V getStringValue(Object node, String key, Function<String, V> converter) {
String value = getString(node, key);
if (value != null && value.length() > 0) {
if (value != null) {
try {
return converter.apply(getString(node, key));
} catch (Exception e) {
@ -95,4 +96,33 @@ public class JsonUtilities {
return null;
}
public static <K extends Enum<K>> EnumMap<K, String> mapStringValues(Object node, Class<K> cls) {
return mapValues(node, cls, JsonUtilities::nonEmptyOrNull);
}
public static <K extends Enum<K>, V> EnumMap<K, V> mapValues(Object node, Class<K> cls, Function<Object, V> converter) {
Map<?, ?> values = asMap(node);
EnumMap<K, V> map = new EnumMap<K, V>(cls);
for (K key : cls.getEnumConstants()) {
Object object = values.get(key.name());
if (object != null) {
V value = converter.apply(object);
if (value != null) {
map.put(key, value);
}
}
}
return map;
}
public static String nonEmptyOrNull(Object object) {
if (object != null) {
String string = object.toString();
if (string.length() > 0) {
return string;
}
}
return null;
}
}

View File

@ -109,8 +109,9 @@ public class AcoustIDClient implements MusicIdentificationService {
public AudioTrack parseResult(String json, final int targetDuration) throws IOException {
Object data = readJson(json);
if (!"ok".equals(getString(data, "status"))) {
throw new IOException("acoustid responded with error: " + getString(data, "status"));
String status = getString(data, "status");
if (!"ok".equals(status)) {
throw new IOException(String.format("%s responded with error: %s", getName(), status));
}
for (Object result : getArray(data, "results")) {

View File

@ -45,17 +45,10 @@ public class FanartTVClient {
return asMap(json).entrySet().stream().flatMap(it -> {
return streamJsonObjects(it.getValue()).map(item -> {
Map<FanartProperty, String> fields = new EnumMap<FanartProperty, String>(FanartProperty.class);
fields.put(FanartProperty.type, it.getKey().toString());
Map<FanartProperty, String> map = mapStringValues(item, FanartProperty.class);
map.put(FanartProperty.type, it.getKey().toString());
for (FanartProperty prop : FanartProperty.values()) {
Object value = item.get(prop.name());
if (value != null) {
fields.put(prop, value.toString());
}
}
return new FanartDescriptor(fields);
return new FanartDescriptor(map);
}).filter(art -> art.getProperties().size() > 1);
}).toArray(FanartDescriptor[]::new);
}

View File

@ -1,8 +1,11 @@
package net.filebot.web;
import static java.nio.charset.StandardCharsets.*;
import static java.util.Arrays.*;
import static java.util.Collections.*;
import static java.util.stream.Collectors.*;
import static net.filebot.util.FileUtilities.*;
import static net.filebot.util.JsonUtilities.*;
import java.io.File;
import java.io.IOException;
@ -13,10 +16,8 @@ import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
@ -30,10 +31,6 @@ import net.filebot.Cache;
import net.filebot.CacheType;
import net.filebot.ResourceManager;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
public class ShooterSubtitles implements VideoHashSubtitleService {
private static final String LANGUAGE_CHINESE = "Chinese";
@ -97,30 +94,16 @@ public class ShooterSubtitles implements VideoHashSubtitleService {
}
ByteBuffer post = WebRequest.post(endpoint, param, null);
Object response = JSONValue.parse(StandardCharsets.UTF_8.decode(post).toString());
Object response = readJson(UTF_8.decode(post));
List<SubtitleDescriptor> results = new ArrayList<SubtitleDescriptor>();
String name = getNameWithoutExtension(file.getName());
JSON: for (JSONObject result : jsonList(response)) {
if (result == null)
continue;
List<JSONObject> files = jsonList(result.get("Files"));
if (files.size() == 1) {
for (JSONObject fd : jsonList(result.get("Files"))) {
String type = (String) fd.get("Ext");
String link = (String) fd.get("Link");
results.add(new ShooterSubtitleDescriptor(name, type, link, languageName));
// use the first best option and ignore the rest
break JSON;
}
}
}
cache.put(key, results.toArray(new SubtitleDescriptor[0]));
return results;
// use the first best option and ignore the rest
return streamJsonObjects(response).flatMap(it -> streamJsonObjects(it, "Files")).map(f -> {
String type = getString(f, "Ext");
String link = getString(f, "Link");
return new ShooterSubtitleDescriptor(name, type, link, languageName);
}).limit(1).collect(toList());
}
@Override
@ -133,21 +116,6 @@ public class ShooterSubtitles implements VideoHashSubtitleService {
throw new UnsupportedOperationException();
}
protected static List<JSONObject> jsonList(final Object array) {
return new AbstractList<JSONObject>() {
@Override
public JSONObject get(int index) {
return (JSONObject) ((JSONArray) array).get(index);
}
@Override
public int size() {
return array == null ? 0 : ((JSONArray) array).size();
}
};
}
/**
*
* @see https://docs.google.com/document/d/1w5MCBO61rKQ6hI5m9laJLWse__yTYdRugpVyz4RzrmM/preview

View File

@ -2,7 +2,10 @@ package net.filebot.web;
import static java.util.Arrays.*;
import static java.util.Collections.*;
import static java.util.stream.Collectors.*;
import static net.filebot.CachedResource2.*;
import static net.filebot.Logging.*;
import static net.filebot.util.JsonUtilities.*;
import static net.filebot.util.StringUtilities.*;
import static net.filebot.web.WebRequest.*;
@ -11,23 +14,22 @@ import java.io.FileNotFoundException;
import java.io.Serializable;
import java.net.URI;
import java.net.URL;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import javax.swing.Icon;
@ -38,10 +40,6 @@ import net.filebot.ResourceManager;
import net.filebot.web.TMDbClient.MovieInfo.MovieProperty;
import net.filebot.web.TMDbClient.Person.PersonProperty;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
public class TMDbClient implements MovieIdentificationService {
private static final String host = "api.themoviedb.org";
@ -89,31 +87,21 @@ public class TMDbClient implements MovieIdentificationService {
param.put("year", movieYear);
}
JSONObject response = request("search/movie", param, locale, SEARCH_LIMIT);
List<Movie> result = new ArrayList<Movie>();
Map<?, ?> response = request("search/movie", param, locale, SEARCH_LIMIT);
for (JSONObject it : jsonList(response.get("results"))) {
if (it == null) {
continue;
}
// e.g.
// {"id":16320,"title":"冲出宁静号","release_date":"2005-09-30","original_title":"Serenity"}
// e.g. {"id":16320,"title":"冲出宁静号","release_date":"2005-09-30","original_title":"Serenity"}
return streamJsonObjects(response, "results").map(it -> {
int id = -1, year = -1;
String title = (String) it.get("title");
String originalTitle = (String) it.get("original_title");
if (title == null || title.isEmpty()) {
String title = getString(it, "title");
String originalTitle = getString(it, "original_title");
if (title == null) {
title = originalTitle;
}
try {
id = Float.valueOf(it.get("id").toString()).intValue();
try {
String release = (String) it.get("release_date");
year = matchInteger(release);
} catch (Exception e) {
throw new IllegalArgumentException("Missing data: release date");
}
id = getDecimal(it, "id").intValue();
year = matchInteger(getString(it, "release_date")); // release date is often missing
Set<String> alternativeTitles = new LinkedHashSet<String>();
if (originalTitle != null) {
@ -122,32 +110,24 @@ public class TMDbClient implements MovieIdentificationService {
if (extendedInfo) {
try {
Set<String> internationalTitles = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
JSONObject titles = request("movie/" + id + "/alternative_titles", null, null, REQUEST_LIMIT);
for (JSONObject node : jsonList(titles.get("titles"))) {
String t = (String) node.get("title");
if (t != null && t.length() >= 3) {
internationalTitles.add(t);
}
}
alternativeTitles.addAll(internationalTitles);
Map<?, ?> titles = request("movie/" + id + "/alternative_titles", null, null, REQUEST_LIMIT);
streamJsonObjects(titles, "titles").map(n -> {
return getString(n, "title");
}).filter(t -> t != null && t.length() >= 3).forEach(alternativeTitles::add);
} catch (Exception e) {
Logger.getLogger(TMDbClient.class.getName()).log(Level.WARNING, String.format("Unable to retrieve alternative titles [%s]: %s", title, e.getMessage()));
debug.warning(format("Failed to fetch alternative titles for %s [%d] => %s", title, id, e));
}
}
// make sure main title is not in the set of alternative titles
alternativeTitles.remove(title);
result.add(new Movie(title, alternativeTitles.toArray(new String[0]), year, -1, id, locale));
return new Movie(title, alternativeTitles.toArray(new String[0]), year, -1, id, locale);
} catch (Exception e) {
// only print 'missing release date' warnings for matching movie titles
if (movieName.equalsIgnoreCase(title) || movieName.equalsIgnoreCase(originalTitle)) {
Logger.getLogger(TMDbClient.class.getName()).log(Level.WARNING, String.format("Ignore movie metadata: %s [%d]: %s", title, id, e.getMessage()));
}
debug.warning(format("Bad data: %s", it));
return null;
}
}
return result;
}).filter(Objects::nonNull).collect(toList());
}
public URI getMoviePageLink(int tmdbid) {
@ -189,137 +169,100 @@ public class TMDbClient implements MovieIdentificationService {
}
public MovieInfo getMovieInfo(String id, Locale locale, boolean extendedInfo) throws Exception {
JSONObject response = request("movie/" + id, extendedInfo ? singletonMap("append_to_response", "alternative_titles,releases,casts,trailers") : null, locale, REQUEST_LIMIT);
Map<?, ?> response = request("movie/" + id, extendedInfo ? singletonMap("append_to_response", "alternative_titles,releases,casts,trailers") : null, locale, REQUEST_LIMIT);
Map<MovieProperty, String> fields = new EnumMap<MovieProperty, String>(MovieProperty.class);
for (MovieProperty key : MovieProperty.values()) {
Object value = response.get(key.name());
if (value != null) {
fields.put(key, value.toString());
}
}
Map<MovieProperty, String> fields = mapStringValues(response, MovieProperty.class);
try {
JSONObject collection = (JSONObject) response.get("belongs_to_collection");
fields.put(MovieProperty.collection, (String) collection.get("name"));
Map<?, ?> collection = getMap(response, "belongs_to_collection");
fields.put(MovieProperty.collection, getString(collection, "name"));
} catch (Exception e) {
// movie doesn't belong to any collection
// movie does not belong to any collection
debug.warning(format("Bad data: belongs_to_collection => %s", response));
}
List<String> genres = new ArrayList<String>();
try {
for (JSONObject it : jsonList(response.get("genres"))) {
String name = (String) it.get("name");
if (name != null && name.length() > 0) {
genres.add(name);
}
}
streamJsonObjects(response, "genres").map(it -> getString(it, "name")).filter(Objects::nonNull).forEach(genres::add);
} catch (Exception e) {
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Illegal genres data: " + response);
debug.warning(format("Bad data: genres => %s", response));
}
List<String> spokenLanguages = new ArrayList<String>();
try {
for (JSONObject it : jsonList(response.get("spoken_languages"))) {
spokenLanguages.add((String) it.get("iso_639_1"));
}
streamJsonObjects(response, "spoken_languages").map(it -> getString(it, "iso_639_1")).filter(Objects::nonNull).forEach(spokenLanguages::add);
} catch (Exception e) {
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Illegal spoken_languages data: " + response);
debug.warning(format("Bad data: spoken_languages => %s", response));
}
List<String> productionCountries = new ArrayList<String>();
try {
for (JSONObject it : jsonList(response.get("production_countries"))) {
productionCountries.add((String) it.get("iso_3166_1"));
}
streamJsonObjects(response, "production_countries").map(it -> getString(it, "iso_3166_1")).filter(Objects::nonNull).forEach(productionCountries::add);
} catch (Exception e) {
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Illegal production_countries data: " + response);
debug.warning(format("Bad data: production_countries => %s", response));
}
List<String> productionCompanies = new ArrayList<String>();
try {
for (JSONObject it : jsonList(response.get("production_companies"))) {
productionCompanies.add((String) it.get("name"));
}
streamJsonObjects(response, "production_companies").map(it -> getString(it, "name")).filter(Objects::nonNull).forEach(productionCompanies::add);
} catch (Exception e) {
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Illegal production_companies data: " + response);
debug.warning(format("Bad data: production_companies => %s", response));
}
List<String> alternativeTitles = new ArrayList<String>();
try {
JSONObject titles = (JSONObject) response.get("alternative_titles");
if (titles != null) {
for (JSONObject it : jsonList(titles.get("titles"))) {
alternativeTitles.add((String) it.get("title"));
}
}
streamJsonObjects(getMap(response, "alternative_titles"), "titles").map(it -> getString(it, "title")).filter(Objects::nonNull).forEach(alternativeTitles::add);
} catch (Exception e) {
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Illegal alternative_titles data: " + response);
debug.warning(format("Bad data: alternative_titles => %s", response));
}
Map<String, String> certifications = new HashMap<String, String>();
Map<String, String> certifications = new LinkedHashMap<String, String>();
try {
String countryCode = locale.getCountry().isEmpty() ? "US" : locale.getCountry();
JSONObject releases = (JSONObject) response.get("releases");
if (releases != null) {
for (JSONObject it : jsonList(releases.get("countries"))) {
String certificationCountry = (String) it.get("iso_3166_1");
String certification = (String) it.get("certification");
if (certification != null && certificationCountry != null && certification.length() > 0 && certificationCountry.length() > 0) {
// add country specific certification code
if (countryCode.equals(certificationCountry)) {
fields.put(MovieProperty.certification, certification);
}
// collect all certification codes just in case
certifications.put(certificationCountry, certification);
streamJsonObjects(getMap(response, "releases"), "countries").forEach(it -> {
String certificationCountry = getString(it, "iso_3166_1");
String certification = getString(it, "certification");
if (certification != null && certificationCountry != null) {
// add country specific certification code
if (countryCode.equals(certificationCountry)) {
fields.put(MovieProperty.certification, certification);
}
// collect all certification codes just in case
certifications.put(certificationCountry, certification);
}
}
});
} catch (Exception e) {
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Illegal releases data: " + response);
debug.warning(format("Bad data: certification => %s", response));
}
List<Person> cast = new ArrayList<Person>();
try {
JSONObject castResponse = (JSONObject) response.get("casts");
if (castResponse != null) {
for (String section : new String[] { "cast", "crew" }) {
for (JSONObject it : jsonList(castResponse.get(section))) {
Map<PersonProperty, String> person = new EnumMap<PersonProperty, String>(PersonProperty.class);
for (PersonProperty key : PersonProperty.values()) {
Object value = it.get(key.name());
if (value != null) {
person.put(key, value.toString());
}
}
cast.add(new Person(person));
}
}
}
Stream.of("cast", "crew").flatMap(section -> streamJsonObjects(getMap(response, "casts"), section)).map(it -> {
return mapStringValues(it, PersonProperty.class);
}).map(Person::new).forEach(cast::add);
} catch (Exception e) {
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Illegal casts data: " + response);
debug.warning(format("Bad data: casts => %s", response));
}
List<Trailer> trailers = new ArrayList<Trailer>();
try {
JSONObject trailerResponse = (JSONObject) response.get("trailers");
if (trailerResponse != null) {
for (String section : new String[] { "quicktime", "youtube" }) {
for (JSONObject it : jsonList(trailerResponse.get(section))) {
Map<String, String> sources = new LinkedHashMap<String, String>();
if (it.containsKey("sources")) {
for (JSONObject s : jsonList(it.get("sources"))) {
sources.put(s.get("size").toString(), s.get("source").toString());
}
} else {
sources.put(it.get("size").toString(), it.get("source").toString());
Stream.of("quicktime", "youtube").forEach(section -> {
streamJsonObjects(getMap(response, "trailers"), section).map(it -> {
Map<String, String> sources = new LinkedHashMap<String, String>();
Stream.concat(Stream.of(it), streamJsonObjects(it, "sources")).forEach(source -> {
String size = getString(source, "size");
if (size != null) {
sources.put(size, getString(source, "source"));
}
trailers.add(new Trailer(section, it.get("name").toString(), sources));
}
}
}
});
return new Trailer(section, getString(it, "name"), sources);
}).forEach(trailers::add);
});
} catch (Exception e) {
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Illegal trailers data: " + response);
debug.warning(format("Bad data: trailers => %s", response));
}
return new MovieInfo(fields, alternativeTitles, genres, certifications, spokenLanguages, productionCountries, productionCompanies, cast, trailers);
@ -327,30 +270,29 @@ public class TMDbClient implements MovieIdentificationService {
public List<Artwork> getArtwork(String id) throws Exception {
// http://api.themoviedb.org/3/movie/11/images
JSONObject config = request("configuration", null, null, REQUEST_LIMIT);
String baseUrl = (String) ((JSONObject) config.get("images")).get("base_url");
Map<?, ?> config = request("configuration", null, null, REQUEST_LIMIT);
String baseUrl = getString(getMap(config, "images"), "base_url");
JSONObject images = request("movie/" + id + "/images", null, null, REQUEST_LIMIT);
List<Artwork> artwork = new ArrayList<Artwork>();
Map<?, ?> images = request("movie/" + id + "/images", null, null, REQUEST_LIMIT);
for (String section : new String[] { "backdrops", "posters" }) {
for (JSONObject it : jsonList(images.get(section))) {
return Stream.of("backdrops", "posters").flatMap(section -> {
Stream<Artwork> artwork = streamJsonObjects(images, section).map(it -> {
try {
String url = baseUrl + "original" + (String) it.get("file_path");
int width = Float.valueOf(it.get("width").toString()).intValue();
int height = Float.valueOf(it.get("height").toString()).intValue();
String lang = (String) it.get("iso_639_1");
artwork.add(new Artwork(section, new URL(url), width, height, lang));
String url = baseUrl + "original" + getString(it, "file_path");
int width = getDecimal(it, "width").intValue();
int height = getDecimal(it, "height").intValue();
String lang = getString(it, "iso_639_1");
return new Artwork(section, new URL(url), width, height, lang);
} catch (Exception e) {
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Invalid artwork: " + it, e);
debug.warning(format("Bad artwork: %s => %s", it, e));
return null;
}
}
}
return artwork;
});
return artwork;
}).collect(toList());
}
public JSONObject request(String resource, Map<String, Object> parameters, Locale locale, final FloodLimit limit) throws Exception {
public Map<?, ?> request(String resource, Map<String, Object> parameters, Locale locale, final FloodLimit limit) throws Exception {
// default parameters
LinkedHashMap<String, Object> data = new LinkedHashMap<String, Object>();
if (parameters != null) {
@ -375,34 +317,19 @@ public class TMDbClient implements MovieIdentificationService {
Cache etagStorage = Cache.getCache("etag", CacheType.Monthly);
Cache cache = Cache.getCache(getName(), CacheType.Monthly);
String json = cache.text(key, s -> getResource(s)).fetch(withPermit(fetchIfNoneMatch(etagStorage), r -> REQUEST_LIMIT.acquirePermit() != null)).expire(Cache.ONE_WEEK).get();
Object json = cache.json(key, s -> getResource(s)).fetch(withPermit(fetchIfNoneMatch(etagStorage), r -> limit.acquirePermit() != null)).expire(Cache.ONE_WEEK).get();
JSONObject object = (JSONObject) JSONValue.parse(json);
if (object == null || object.isEmpty()) {
throw new FileNotFoundException("Resource not found: " + getResource(key));
Map<?, ?> root = asMap(json);
if (root.isEmpty()) {
throw new FileNotFoundException(String.format("Resource is empty: %s => %s", json, getResource(key)));
}
return object;
return root;
}
public URL getResource(String file) throws Exception {
return new URL("http", host, "/" + version + "/" + file);
}
protected List<JSONObject> jsonList(final Object array) {
return new AbstractList<JSONObject>() {
@Override
public JSONObject get(int index) {
return (JSONObject) ((JSONArray) array).get(index);
}
@Override
public int size() {
return ((JSONArray) array).size();
}
};
}
public static class MovieInfo implements Serializable {
public static enum MovieProperty {
@ -429,7 +356,7 @@ public class TMDbClient implements MovieIdentificationService {
this.fields = new EnumMap<MovieProperty, String>(fields);
this.alternativeTitles = alternativeTitles.toArray(new String[0]);
this.genres = genres.toArray(new String[0]);
this.certifications = new HashMap<String, String>(certifications);
this.certifications = new LinkedHashMap<String, String>(certifications);
this.spokenLanguages = spokenLanguages.toArray(new String[0]);
this.productionCountries = productionCountries.toArray(new String[0]);
this.productionCompanies = productionCompanies.toArray(new String[0]);